diff --git a/README.md b/README.md
index 72aebac11..4cda70629 100644
--- a/README.md
+++ b/README.md
@@ -586,7 +586,7 @@ yours to keep:
✅ |
-❌ |
+✅ |
❌ |
@@ -602,7 +602,7 @@ yours to keep:
✅ |
-❌ |
+✅ |
❌ |
@@ -618,7 +618,7 @@ yours to keep:
✅ |
-❌ |
+✅ |
❌ |
diff --git a/bench/Cargo.toml b/bench/Cargo.toml
index 43637e527..ae65f238c 100644
--- a/bench/Cargo.toml
+++ b/bench/Cargo.toml
@@ -15,6 +15,12 @@ publish = false
# `src/cpu.rs` and `ci/bench_arches.py`).
verified-garbage = { path = "..", features = ["cpu-features-env"] }
+[features]
+# OpenSSL 3.2+ supplies Argon2; the benchmark runners use 3.0.
+# On a supported host, use `cargo bench --features openssl-argon2` and
+# `cargo test --features openssl-argon2 --test argon2` for the comparison.
+openssl-argon2 = []
+
[dev-dependencies]
criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support"] }
openssl = "0.10"
diff --git a/bench/benches/primitives/argon2.rs b/bench/benches/primitives/argon2.rs
new file mode 100644
index 000000000..9e334689f
--- /dev/null
+++ b/bench/benches/primitives/argon2.rs
@@ -0,0 +1,74 @@
+//! Complete Argon2 derivations, including allocation and initialization.
+//!
+//! OpenSSL supplies Argon2 from version 3.2; the runners use 3.0. Enable
+//! `openssl-argon2` on a supported host to benchmark it alongside this library.
+
+use criterion::Criterion;
+
+pub const USES: &[&str] = &["argon2", "blake2b"];
+
+#[cfg(target_arch = "x86_64")]
+pub fn bench(c: &mut Criterion) {
+ use std::hint::black_box;
+
+ use criterion::BenchmarkId;
+ use verified_garbage::argon2::{Variant, derive};
+
+ use crate::VG;
+ for (variant, name) in [
+ (Variant::Argon2d, "argon2d"),
+ (Variant::Argon2i, "argon2i"),
+ (Variant::Argon2id, "argon2id"),
+ ] {
+ let mut g = c.benchmark_group(name);
+ g.sample_size(10);
+ for memory in [1024u32, 16384] {
+ let mut out = [0u8; 32];
+ g.bench_function(BenchmarkId::new(VG, memory), |b| {
+ b.iter(|| {
+ derive(
+ variant,
+ black_box(b"password"),
+ black_box(b"saltsalt"),
+ 3,
+ memory,
+ 1,
+ 1,
+ b"",
+ b"",
+ &mut out,
+ )
+ .unwrap()
+ })
+ });
+ #[cfg(feature = "openssl-argon2")]
+ {
+ let openssl = match variant {
+ Variant::Argon2d => openssl::kdf::argon2d,
+ Variant::Argon2i => openssl::kdf::argon2i,
+ Variant::Argon2id => openssl::kdf::argon2id,
+ };
+ g.bench_function(BenchmarkId::new(crate::OPENSSL, memory), |b| {
+ b.iter(|| {
+ openssl(
+ None,
+ black_box(b"password"),
+ black_box(b"saltsalt"),
+ None,
+ None,
+ 3,
+ 1,
+ memory,
+ &mut out,
+ )
+ .unwrap()
+ })
+ });
+ }
+ }
+ g.finish();
+ }
+}
+
+#[cfg(not(target_arch = "x86_64"))]
+pub fn bench(_: &mut Criterion) {}
diff --git a/bench/benches/primitives/main.rs b/bench/benches/primitives/main.rs
index 7eb3b985c..0f3f12bda 100644
--- a/bench/benches/primitives/main.rs
+++ b/bench/benches/primitives/main.rs
@@ -17,6 +17,7 @@ use openssl::pkey::PKey;
use openssl::sign::Signer;
mod aes_gcm;
+mod argon2;
mod blake2b;
mod blake2s;
mod chacha20;
@@ -236,6 +237,7 @@ const BENCHES: &[Bench] = &[
(poly1305::USES, poly1305::bench),
(rc2_cbc::USES, rc2_cbc::bench),
(triple_des_ecb::USES, triple_des_ecb::bench),
+ (argon2::USES, argon2::bench),
(scrypt::USES, scrypt::bench),
(sha1::USES, sha1::bench),
(sha224::USES, sha224::bench),
diff --git a/bench/tests/argon2.rs b/bench/tests/argon2.rs
new file mode 100644
index 000000000..0440b141a
--- /dev/null
+++ b/bench/tests/argon2.rs
@@ -0,0 +1,69 @@
+//! Differential complete derivations against OpenSSL, including H′ boundaries.
+
+#![cfg(all(target_arch = "x86_64", feature = "openssl-argon2"))]
+
+use verified_garbage::argon2::{Variant, derive};
+
+type Oracle = fn(
+ Option<&openssl::lib_ctx::LibCtxRef>,
+ &[u8],
+ &[u8],
+ Option<&[u8]>,
+ Option<&[u8]>,
+ u32,
+ u32,
+ u32,
+ &mut [u8],
+) -> Result<(), openssl::error::ErrorStack>;
+
+#[test]
+fn matches_openssl() {
+ for (variant, oracle) in [
+ (Variant::Argon2d, openssl::kdf::argon2d as Oracle),
+ (Variant::Argon2i, openssl::kdf::argon2i),
+ (Variant::Argon2id, openssl::kdf::argon2id),
+ ] {
+ for (lanes, memory) in [(1, 8), (1, 9), (2, 16), (3, 25), (2, 1040)] {
+ for iterations in [1, 2] {
+ for length in [4, 32, 64, 65, 96, 128] {
+ for (password, secret, ad) in [
+ (&b""[..], &b""[..], &b""[..]),
+ (&b"password"[..], &b"secret"[..], &b"associated data"[..]),
+ ] {
+ let mut expected = vec![0; length];
+ oracle(
+ None,
+ password,
+ b"saltsalt",
+ Some(ad),
+ Some(secret),
+ iterations,
+ lanes,
+ memory,
+ &mut expected,
+ )
+ .unwrap();
+ let mut actual = vec![0; length];
+ derive(
+ variant,
+ password,
+ b"saltsalt",
+ iterations,
+ memory,
+ lanes,
+ 1,
+ secret,
+ ad,
+ &mut actual,
+ )
+ .unwrap();
+ assert_eq!(
+ actual, expected,
+ "{variant:?}, lanes={lanes}, memory={memory}, passes={iterations}, length={length}"
+ );
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/lean/VerifiedGarbage/Generic/Blake2b/X86_64/Argon2.lean b/lean/VerifiedGarbage/Generic/Blake2b/X86_64/Argon2.lean
index 97092aebd..c0625664c 100644
--- a/lean/VerifiedGarbage/Generic/Blake2b/X86_64/Argon2.lean
+++ b/lean/VerifiedGarbage/Generic/Blake2b/X86_64/Argon2.lean
@@ -1,4 +1,4 @@
-import VerifiedGarbage.Proof.Argon2.X86_64.HPrime.Verified
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveVerified
/-! # Argon2 H′ for every x86-64 BLAKE2b backend -/
@@ -16,6 +16,19 @@ def artifacts (v : Proof.Blake2.X86_64.Backend) : List Artifact := [
stack := 16
verified := Proof.Argon2.X86_64.HPrime.verified v
spSafe := Proof.Argon2.X86_64.HPrime.spSafe v
+ features := v.features },
+ { Spec.Argon2.deriveApi with
+ name := Spec.Argon2.deriveApi.name ++ v.suffix
+ target := VG.X86_64.target
+ doc := Spec.Argon2.deriveApi.doc
+ (notes := ["Serial lane evaluation honors every positive worker limit. All hashing uses \
+ the selected BLAKE2b streaming backend, including H₀ and every H′ call."])
+ code := Impl.Argon2.X86_64.Derive.code (Spec.Argon2.hPrimeApi.name ++ v.suffix)
+ (Proof.Argon2.X86_64.HPrime.hash v)
+ contract := Spec.Argon2.deriveContract VG.X86_64.abi 344
+ stack := 344
+ verified := Proof.Argon2.X86_64.Derive.verified v _
+ spSafe := Proof.Argon2.X86_64.Derive.code_spSafe v _
features := v.features }]
end VG.Generic.Blake2b.X86_64.Argon2
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCache.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCache.lean
new file mode 100644
index 000000000..f0d7f3a3d
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCache.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressCalls
+
+/-! Cache one address block per 128 segment positions. Frame offset eight holds
+its one-based counter; initializing it to zero forces generation even when the
+first filled index is two. Only public counters control regeneration.
+-/
+
+namespace VG.Impl.Argon2.X86_64.AddressCache
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def check : List Instr := [
+ .mov .rax (.reg .r15), .shift .shr .rax 7, .alu .add .rax (.imm 1),
+ .alu .cmp .rax (.mem (at_ .rbp 8))]
+
+def save : List Instr := [.store (at_ .rbp 8) .rax]
+
+def select : Prog isa := .seq (.block check)
+ (.ite .e (.block []) (.seq (.block save) AddressCalls.code))
+
+def wordArgs : List Instr := [
+ .mov .rcx (.mem (at_ .rbp 248)), .mov .rax (.reg .r15), .alu .and .rax (.imm 127)]
+
+def wordRead : List Instr := [
+ .mov .rdi (.mem { base := .rcx, index := some .rax, scale := 8, disp := 6144 })]
+
+def word : Prog isa := .seq (.block wordArgs) (.block wordRead)
+
+def code : Prog isa := .seq select word
+
+end VG.Impl.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCalls.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCalls.lean
new file mode 100644
index 000000000..df34e03ec
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressCalls.lean
@@ -0,0 +1,37 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressHeader
+import VerifiedGarbage.Impl.Argon2.X86_64.ClearBlock
+import VerifiedGarbage.Spec.Argon2.Contract
+
+/-! Independent-address generation in the shared 16 KiB scratch allocation.
+G uses `[0,4096)`, temporary output `[4096,5120)`, input `[5120,6144)`,
+address output `[6144,7168)`, and the zero block `[7168,8192)`. Every stage
+reloads the scratch pointer from frame offset 248 after a compression call.
+-/
+
+namespace VG.Impl.Argon2.X86_64.AddressCalls
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def pointer (offset : Nat) : List Instr := [
+ .mov .rdi (.mem (at_ .rbp 248)), .alu .add .rdi (.imm (BitVec.ofNat 32 offset))]
+
+def args (x y out : Nat) : List Instr := [
+ .mov .rcx (.mem (at_ .rbp 248)),
+ .mov .rdi (.reg .rcx), .alu .add .rdi (.imm (BitVec.ofNat 32 x)),
+ .mov .rsi (.reg .rcx), .alu .add .rsi (.imm (BitVec.ofNat 32 y)),
+ .mov .rdx (.reg .rcx), .alu .add .rdx (.imm (BitVec.ofNat 32 out))]
+
+def stage (x y out : Nat) : Prog isa := .seq (.block (args x y out))
+ (.call Spec.Argon2.compressApi.name VG.Impl.Argon2.X86_64.compress)
+
+def calls : Prog isa := .seq (stage 7168 5120 4096) (stage 7168 4096 6144)
+
+def clearAt (offset : Nat) : Prog isa := .seq (.block (pointer offset)) ClearBlock.code
+
+def prepare : Prog isa := .seq (clearAt 5120) (.seq (clearAt 7168)
+ (.seq (.block (pointer 5120)) AddressHeader.code))
+
+def code : Prog isa := .seq prepare calls
+
+end VG.Impl.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressHeader.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressHeader.lean
new file mode 100644
index 000000000..a05863e8d
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressHeader.lean
@@ -0,0 +1,31 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Fill the first seven words of an independently generated address input.
+The input pointer is `rdi`; its remaining words were cleared once. The frame
+holds pass (0), address counter (8), passes (72), variant (112), blocks (240).
+Lane and slice remain in `rbx` and `r14`. The counter is supplied after the
+public address-generation loop advances it to its one-based value.
+-/
+
+namespace VG.Impl.Argon2.X86_64.AddressHeader
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def registerWord (i : Nat) (r : Reg) : List Instr := [.store (at_ .rdi (8 * i)) r]
+
+def frameWord (i offset : Nat) : List Instr :=
+ [.mov .rax (.mem (at_ .rbp offset)), .store (at_ .rdi (8 * i)) .rax]
+
+def frameOffset (i : Nat) : Nat :=
+ if i = 0 then 0 else if i = 3 then 240 else if i = 4 then 72 else if i = 5 then 112 else 8
+
+def field (i : Nat) : List Instr :=
+ if i = 1 then registerWord i .rbx else if i = 2 then registerWord i .r14
+ else frameWord i (frameOffset i)
+
+def fields (n : Nat) : List Instr := (List.range n).flatMap field
+
+def code : Prog isa := .block (fields 7)
+
+end VG.Impl.Argon2.X86_64.AddressHeader
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressMode.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressMode.lean
new file mode 100644
index 000000000..b5adabb1a
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/AddressMode.lean
@@ -0,0 +1,27 @@
+import VerifiedGarbage.TCB.X86_64.Isa
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Determine the segment's address mode from public variant, pass and slice.
+The mask in `r10` is one for independent addressing and zero otherwise.
+-/
+
+namespace VG.Impl.Argon2.X86_64.AddressMode
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def kind : List Instr := [
+ .mov .rax (.mem (at_ .rbp 112)),
+ .mov .r10 (.reg .rax), .alu .xor .r10 (.imm 1), .alu .cmp .r10 (.imm 1), .alu .sbb .r10 (.reg .r10),
+ .mov .r8 (.reg .rax), .alu .xor .r8 (.imm 2), .alu .cmp .r8 (.imm 1), .alu .sbb .r8 (.reg .r8)]
+
+def pass : List Instr := [
+ .mov .r9 (.mem (at_ .rbp 0)), .alu .cmp .r9 (.imm 1), .alu .sbb .r9 (.reg .r9)]
+
+def slice : List Instr := [
+ .alu .cmp .r14 (.imm 2), .alu .sbb .r11 (.reg .r11),
+ .alu .and .r8 (.reg .r9), .alu .and .r8 (.reg .r11), .alu .or .r10 (.reg .r8), .alu .and .r10 (.imm 1)]
+
+def code : Prog isa := .seq (.block kind) (.seq (.block pass) (.block slice))
+
+end VG.Impl.Argon2.X86_64.AddressMode
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/BlockAddress.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/BlockAddress.lean
new file mode 100644
index 000000000..a259440e0
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/BlockAddress.lean
@@ -0,0 +1,20 @@
+import VerifiedGarbage.TCB.X86_64.Isa
+
+/-! Lane-major matrix addressing. The matrix base is in `r8`, the lane
+in `rax`, the column in `rcx`, and the lane length in `r12`. The resulting
+block pointer is returned in `rax`. Scalar multiplication and ten doublings
+work on the baseline ISA, including when the reference coordinates are secret.
+-/
+
+namespace VG.Impl.Argon2.X86_64.BlockAddress
+
+open VG.X86_64
+
+def flatten : List Instr := [.mul .r12, .alu .add .rax (.reg .rcx)]
+
+def scale : List Instr := List.replicate 10 (.alu .add .rax (.reg .rax))
+
+def code : Prog isa :=
+ .seq (.block flatten) (.seq (.block scale) (.block [.alu .add .rax (.reg .r8)]))
+
+end VG.Impl.Argon2.X86_64.BlockAddress
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ClearBlock.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ClearBlock.lean
new file mode 100644
index 000000000..856a8bbde
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ClearBlock.lean
@@ -0,0 +1,18 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Clear one 1024-byte address-generation block. The destination in `rdi`
+is public; neither the old contents nor any input value affects the trace.
+-/
+
+namespace VG.Impl.Argon2.X86_64.ClearBlock
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def word (i : Nat) : List Instr := [.store (at_ .rdi (8 * i)) .rax]
+
+def words (n : Nat) : List Instr := (List.range n).flatMap word
+
+def code : Prog isa := .seq (.block [.mov .rax (.imm 0)]) (.block (words 128))
+
+end VG.Impl.Argon2.X86_64.ClearBlock
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/DependentWord.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/DependentWord.lean
new file mode 100644
index 000000000..726cfc16d
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/DependentWord.lean
@@ -0,0 +1,21 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillKernel
+
+/-! Read the previous cell's first word for data-dependent addressing. Only the
+public loop position and matrix base determine the read address.
+-/
+
+namespace VG.Impl.Argon2.X86_64.DependentWord
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def args : List Instr := [.mov .rcx (.reg .rdi), .mov .rax (.reg .rbx)]
+
+def pointer : Prog isa := .seq (.block FillKernel.matrix)
+ (.seq FillColumn.code (.seq (.block args) BlockAddress.code))
+
+def read : List Instr := [.mov .rdi (.mem (at_ .rax 0))]
+
+def code : Prog isa := .seq pointer (.block read)
+
+end VG.Impl.Argon2.X86_64.DependentWord
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/Derive.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Derive.lean
new file mode 100644
index 000000000..a60431a7b
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Derive.lean
@@ -0,0 +1,50 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.InitialBody
+import VerifiedGarbage.Impl.Argon2.X86_64.Parameters
+
+/-! The complete System V entry point, including u32 argument normalization. -/
+
+namespace VG.Impl.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+open VG.Impl.Argon2.X86_64.HPrime (at_)
+
+def saved : List Reg := [.rbx, .rbp, .r12, .r13, .r14, .r15]
+
+def normalize (offset : Nat) : List Instr :=
+ [.mov .rax (.mem (at_ .rbp offset)), .mov32 .rax (.reg .rax), .store (at_ .rbp offset) .rax]
+
+def setup : List Instr :=
+ [.mov .rbp (.reg .rsp), .mov32 .rdi (.reg .rdi), .mov32 .r9 (.reg .r9),
+ .store (at_ .rbp 72) .r9, .store (at_ .rbp 80) .r8,
+ .store (at_ .rbp 88) .rcx, .store (at_ .rbp 96) .rdx,
+ .store (at_ .rbp 104) .rsi, .store (at_ .rbp 112) .rdi,
+ .mov .rbx (.mem (at_ .rbp 248))]
+
+def normalizeArgs : List Nat → Prog isa
+ | [] => .block []
+ | [d] => .block (normalize d)
+ | d :: e :: ds => .seq (.block (normalize d)) (normalizeArgs (e :: ds))
+
+def prepareLocal : Prog isa := .seq (.block setup) (normalizeArgs [176, 184, 192])
+
+/-- Copy the twelve read-only caller stack arguments into private slots.
+The original stack pointer is 320 bytes above this local frame. -/
+def copyArg (j : Nat) : List Instr :=
+ [.mov .rax (.mem (at_ .rsp (328 + 8 * j))), .store (at_ .rsp (176 + 8 * j)) .rax]
+
+def copyArgs : List Instr := (List.range 12).flatMap copyArg
+
+def prepare : Prog isa := .seq (.block copyArgs) prepareLocal
+
+/-- One nested frame per saved register restores every register separately.
+The inner thirty-four words reserve the 272-byte private argument/hash frame. -/
+def frame (body : Prog isa) : List Reg → Prog isa
+ | [] => .frame (.push (List.replicate 34 .rax)) body (.pop .rax 34)
+ | r :: rs => .frame (.push [r]) (frame body rs) (.pop r 1)
+
+def body (name : String) (hash : HPrime.Hash) : Prog isa :=
+ .seq prepare (.seq Parameters.code (InitialBody.code name hash))
+
+def code (name : String) (hash : HPrime.Hash) : Prog isa := frame (body name hash) saved
+
+end VG.Impl.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillBlock.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillBlock.lean
new file mode 100644
index 000000000..159a7fc3f
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillBlock.lean
@@ -0,0 +1,12 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.RandomSource
+import VerifiedGarbage.Impl.Argon2.X86_64.FillKernel
+
+/-! Select the random word and update one active matrix cell. -/
+
+namespace VG.Impl.Argon2.X86_64.FillBlock
+
+open VG.X86_64
+
+def code : Prog isa := .seq RandomSource.code FillKernel.code
+
+end VG.Impl.Argon2.X86_64.FillBlock
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillColumn.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillColumn.lean
new file mode 100644
index 000000000..0eb460e65
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillColumn.lean
@@ -0,0 +1,26 @@
+import VerifiedGarbage.TCB.X86_64.Isa
+
+/-! Current and preceding columns in the filling loop. The public slice,
+segment length and offset are in `r14`, `r13` and `r15`, and the lane length
+is in `r12`. `rcx` receives the current column; `rdi` receives its cyclic
+predecessor. Only the public column-zero test controls a branch.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FillColumn
+
+open VG.X86_64
+
+def current : List Instr := [
+ .mov .rax (.reg .r14), .mul .r13, .mov .rcx (.reg .rax),
+ .alu .add .rcx (.reg .r15)]
+
+def select : Prog isa := .ite .e
+ (.block [.mov .rdi (.reg .r12)]) (.block [.mov .rdi (.reg .rcx)])
+
+def previous : Prog isa :=
+ .seq (.block [.alu .cmp .rcx (.imm 0)])
+ (.seq select (.block [.alu .sub .rdi (.imm 1)]))
+
+def code : Prog isa := .seq (.block current) previous
+
+end VG.Impl.Argon2.X86_64.FillColumn
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillCompress.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillCompress.lean
new file mode 100644
index 000000000..2c3e16236
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillCompress.lean
@@ -0,0 +1,33 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillWrite
+import VerifiedGarbage.Spec.Argon2.Contract
+
+/-! Compress the selected previous/reference blocks and update the current
+matrix cell. Pointer setup supplied `r10` (current), `rdi` (previous), and
+`rsi` (reference). The derivation frame holds the pass at offset zero and
+scratch pointer at offset 248; offset 16 retains the destination across G.
+The first 4096 scratch bytes belong to G, and its output is at offset 4096.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FillCompress
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def saveCurrent : List Instr := [.store (at_ .rbp 16) .r10]
+
+def compressArgs : List Instr := [
+ .mov .rcx (.mem (at_ .rbp 248)), .mov .rdx (.reg .rcx), .alu .add .rdx (.imm 4096)]
+
+def writeArgs : List Instr := [
+ .mov .rdi (.mem (at_ .rbp 16)), .mov .rsi (.mem (at_ .rbp 248)),
+ .alu .add .rsi (.imm 4096), .mov .r9 (.mem (at_ .rbp 0))]
+
+def operation : Prog isa :=
+ .seq (.call Spec.Argon2.compressApi.name VG.Impl.Argon2.X86_64.compress)
+ (.seq (.block writeArgs) FillWrite.code)
+
+def setup : Prog isa := .seq (.block saveCurrent) (.block compressArgs)
+
+def code : Prog isa := .seq setup operation
+
+end VG.Impl.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillFinish.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillFinish.lean
new file mode 100644
index 000000000..b7a915d35
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillFinish.lean
@@ -0,0 +1,13 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillIterations
+import VerifiedGarbage.Impl.Argon2.X86_64.Finish
+
+/-! Complete all filling passes, reduce the lane endings, and compute the final tag. -/
+
+namespace VG.Impl.Argon2.X86_64.FillFinish
+
+open VG.X86_64
+
+def code (name : String) (h : HPrime.Hash) : Prog isa :=
+ .seq FillIterations.loop (Finish.code name h)
+
+end VG.Impl.Argon2.X86_64.FillFinish
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIteration.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIteration.lean
new file mode 100644
index 000000000..02397ec81
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIteration.lean
@@ -0,0 +1,13 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSlices
+
+/-! Reset the slice coordinate before each filling pass. -/
+
+namespace VG.Impl.Argon2.X86_64.FillIteration
+
+open VG.X86_64
+
+def setup : List Instr := [.mov .r14 (.imm 0)]
+
+def code : Prog isa := .seq (.block setup) FillSlices.loop
+
+end VG.Impl.Argon2.X86_64.FillIteration
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIterations.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIterations.lean
new file mode 100644
index 000000000..4b277234d
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillIterations.lean
@@ -0,0 +1,20 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillIteration
+
+/-! Advance the public pass counter stored in the mutable header. -/
+
+namespace VG.Impl.Argon2.X86_64.FillIterations
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def increment : List Instr := [.mov .rax (.mem (at_ .rbp 0)), .alu .add .rax (.imm 1)]
+
+def saveCheck : List Instr := [.store (at_ .rbp 0) .rax, .alu .cmp .rax (.mem (at_ .rbp 72))]
+
+def advance : Prog isa := .seq (.block increment) (.block saveCheck)
+
+def body : Prog isa := .seq FillIteration.code advance
+
+def loop : Prog isa := .loop body .b
+
+end VG.Impl.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillKernel.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillKernel.lean
new file mode 100644
index 000000000..958ad44d8
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillKernel.lean
@@ -0,0 +1,27 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceMap
+import VerifiedGarbage.Impl.Argon2.X86_64.FillPointers
+import VerifiedGarbage.Impl.Argon2.X86_64.FillCompress
+
+/-! Map the random word, prepare matrix pointers, and update one active cell.
+The enclosing loops provide the position in callee-saved registers and the
+frame; `rdi` contains either the cached independent word or the previous cell's
+first word. The lane count and matrix base are reloaded after volatile calls.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FillKernel
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def lanes : List Instr := [.mov .rsi (.mem (at_ .rbp 184))]
+def matrix : List Instr := [.mov .r8 (.mem (at_ .rbp 232))]
+
+def mapping : Prog isa := .seq (.block lanes) ReferenceMap.code
+
+def pointers : Prog isa := .seq (.block matrix) FillPointers.code
+
+def prepare : Prog isa := .seq mapping pointers
+
+def code : Prog isa := .seq prepare FillCompress.code
+
+end VG.Impl.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillLanes.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillLanes.lean
new file mode 100644
index 000000000..e16a3cc70
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillLanes.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.SegmentSetup
+
+/-! Fill one slice's lanes serially, advancing only the public lane coordinate. -/
+
+namespace VG.Impl.Argon2.X86_64.FillLanes
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def advance : List Instr := [.alu .add .rbx (.imm 1), .alu .cmp .rbx (.mem (at_ .rbp 184))]
+
+def body : Prog isa := .seq SegmentSetup.code (.block advance)
+
+def loop : Prog isa := .loop body .b
+
+end VG.Impl.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillPointers.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillPointers.lean
new file mode 100644
index 000000000..9fdfcf4a3
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillPointers.lean
@@ -0,0 +1,35 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.BlockAddress
+import VerifiedGarbage.Impl.Argon2.X86_64.FillColumn
+
+/-! Prepare the block pointers for one filling operation. `r8` is the matrix
+base; `rbx`, `r12`–`r15` retain the loop position. Reference mapping supplied
+the reference lane and column in `r9` and `rdi`. The current pointer is saved
+in `r10`, with the previous and reference pointers in `rdi` and `rsi`.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FillPointers
+
+open VG.X86_64
+
+def saveReference : List Instr := [.mov .rsi (.reg .rdi)]
+
+def currentArgs : List Instr := [.mov .rax (.reg .rbx)]
+
+def previousArgs : List Instr := [
+ .mov .r10 (.reg .rax), .mov .rcx (.reg .rdi), .mov .rax (.reg .rbx)]
+
+def referenceArgs : List Instr := [
+ .mov .r11 (.reg .rax), .mov .rcx (.reg .rsi), .mov .rax (.reg .r9)]
+
+def finishArgs : List Instr := [.mov .rsi (.reg .rax), .mov .rdi (.reg .r11)]
+
+def current : Prog isa := .seq (.block currentArgs) BlockAddress.code
+
+def previous : Prog isa := .seq (.block previousArgs) BlockAddress.code
+
+def reference : Prog isa := .seq (.block referenceArgs) BlockAddress.code
+
+def code : Prog isa := .seq (.block saveReference) (.seq FillColumn.code
+ (.seq current (.seq previous (.seq reference (.block finishArgs)))))
+
+end VG.Impl.Argon2.X86_64.FillPointers
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSegment.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSegment.lean
new file mode 100644
index 000000000..ad175b09f
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSegment.lean
@@ -0,0 +1,15 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillBlock
+
+/-! Advance the public index after each active cell, stopping at the segment length. -/
+
+namespace VG.Impl.Argon2.X86_64.FillSegment
+
+open VG.X86_64
+
+def advance : List Instr := [.alu .add .r15 (.imm 1), .alu .cmp .r15 (.reg .r13)]
+
+def body : Prog isa := .seq FillBlock.code (.block advance)
+
+def loop : Prog isa := .loop body .b
+
+end VG.Impl.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSetup.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSetup.lean
new file mode 100644
index 000000000..b709d4114
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSetup.lean
@@ -0,0 +1,18 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Convert initialization's byte stride to filling dimensions and reset the public pass. -/
+
+namespace VG.Impl.Argon2.X86_64.FillSetup
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def dimensions : List Instr :=
+ [.mov .r12 (.reg .r13), .shift .shr .r12 10, .shift .shr .r13 12]
+
+def reset : List Instr :=
+ [.mov .rax (.imm 0), .store (at_ .rbp 0) .rax, .mov .rbx (.imm 0), .mov .r14 (.imm 0)]
+
+def code : Prog isa := .seq (.block dimensions) (.block reset)
+
+end VG.Impl.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlice.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlice.lean
new file mode 100644
index 000000000..1399c363e
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlice.lean
@@ -0,0 +1,13 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillLanes
+
+/-! Reset the lane coordinate and fill every lane of one slice. -/
+
+namespace VG.Impl.Argon2.X86_64.FillSlice
+
+open VG.X86_64
+
+def setup : List Instr := [.mov .rbx (.imm 0)]
+
+def code : Prog isa := .seq (.block setup) FillLanes.loop
+
+end VG.Impl.Argon2.X86_64.FillSlice
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlices.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlices.lean
new file mode 100644
index 000000000..ce56d0f32
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillSlices.lean
@@ -0,0 +1,15 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSlice
+
+/-! Fill a pass's four slices in order, using the public slice coordinate. -/
+
+namespace VG.Impl.Argon2.X86_64.FillSlices
+
+open VG.X86_64
+
+def advance : List Instr := [.alu .add .r14 (.imm 1), .alu .cmp .r14 (.imm 4)]
+
+def body : Prog isa := .seq FillSlice.code (.block advance)
+
+def loop : Prog isa := .loop body .b
+
+end VG.Impl.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillWrite.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillWrite.lean
new file mode 100644
index 000000000..f6d62956d
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FillWrite.lean
@@ -0,0 +1,24 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Write the compression result into the current matrix block. `rsi` points
+to the temporary result and `rdi` to the matrix destination; `r9` is the public
+pass number. Pass zero copies the result, and later passes XOR the old cell.
+Both paths visit every word in ascending order.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FillWrite
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def word (xorOld : Bool) (i : Nat) : List Instr :=
+ [.mov .rax (.mem (at_ .rsi (8 * i)))] ++
+ (if xorOld then [.alu .xor .rax (.mem (at_ .rdi (8 * i)))] else []) ++
+ [.store (at_ .rdi (8 * i)) .rax]
+
+def words (xorOld : Bool) (n : Nat) : List Instr := (List.range n).flatMap (word xorOld)
+
+def code : Prog isa := .seq (.block [.alu .cmp .r9 (.imm 0)])
+ (.ite .e (.block (words false 128)) (.block (words true 128)))
+
+end VG.Impl.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalOutput.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalOutput.lean
new file mode 100644
index 000000000..d1896b47a
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalOutput.lean
@@ -0,0 +1,19 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.HPrime
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Final H′: matrix block zero is the input, using the derivation's hash backend. -/
+
+namespace VG.Impl.Argon2.X86_64.FinalOutput
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def args : List Instr :=
+ [.mov .rdi (.mem (at_ .rbp 232)), .mov .rsi (.imm 1024),
+ .mov .rdx (.mem (at_ .rbp 256)), .mov .rcx (.mem (at_ .rbp 264)),
+ .mov .r8 (.mem (at_ .rbp 248))]
+
+def code (name : String) (h : HPrime.Hash) : Prog isa :=
+ .seq (.block args) (.call name (HPrime.code h))
+
+end VG.Impl.Argon2.X86_64.FinalOutput
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalReduction.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalReduction.lean
new file mode 100644
index 000000000..162bf000b
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FinalReduction.lean
@@ -0,0 +1,12 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReductionInit
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceLanes
+
+/-! Reduce all lane endings into matrix block zero for the final H′ call. -/
+
+namespace VG.Impl.Argon2.X86_64.FinalReduction
+
+open VG.X86_64
+
+def code : Prog isa := .seq ReductionInit.code ReduceLanes.loop
+
+end VG.Impl.Argon2.X86_64.FinalReduction
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/Finish.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Finish.lean
new file mode 100644
index 000000000..19b08e246
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Finish.lean
@@ -0,0 +1,13 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FinalReduction
+import VerifiedGarbage.Impl.Argon2.X86_64.FinalOutput
+
+/-! The complete final reduction and H′, parameterized by the hash backend. -/
+
+namespace VG.Impl.Argon2.X86_64.Finish
+
+open VG.X86_64
+
+def code (name : String) (h : HPrime.Hash) : Prog isa :=
+ .seq FinalReduction.code (FinalOutput.code name h)
+
+end VG.Impl.Argon2.X86_64.Finish
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/FirstLane.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FirstLane.lean
new file mode 100644
index 000000000..3cad23882
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/FirstLane.lean
@@ -0,0 +1,20 @@
+import VerifiedGarbage.TCB.X86_64.Isa
+
+/-! Force the current lane on the first slice of the first pass.
+
+The pass and slice are public in `r9` and `r14`. The current lane is in
+`rbx`; `r8` initially contains J₂ modulo the lane count. Only the public
+position controls a branch.
+-/
+
+namespace VG.Impl.Argon2.X86_64.FirstLane
+
+open VG.X86_64
+
+def test : List Instr := [.mov .rax (.reg .r9), .alu .or .rax (.reg .r14)]
+
+def current : List Instr := [.mov .r8 (.reg .rbx)]
+
+def code : Prog isa := .seq (.block test) (.ite .e (.block current) (.block []))
+
+end VG.Impl.Argon2.X86_64.FirstLane
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitFill.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitFill.lean
new file mode 100644
index 000000000..ef47ef865
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitFill.lean
@@ -0,0 +1,14 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.MemoryInit
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSetup
+import VerifiedGarbage.Impl.Argon2.X86_64.FillFinish
+
+/-! All memory initialization, filling and finalization after H₀ has been computed. -/
+
+namespace VG.Impl.Argon2.X86_64.InitFill
+
+open VG.X86_64
+
+def code (name : String) (h : HPrime.Hash) : Prog isa :=
+ .seq (MemoryInit.code name h) (.seq FillSetup.code (FillFinish.code name h))
+
+end VG.Impl.Argon2.X86_64.InitFill
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/Initial.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Initial.lean
index a907bbb19..84546709f 100644
--- a/lean/VerifiedGarbage/Impl/Argon2/X86_64/Initial.lean
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Initial.lean
@@ -18,8 +18,8 @@ namespace VG.Impl.Argon2.X86_64.Initial
open VG.X86_64
open VG.Impl.Argon2.X86_64.HPrime (Hash at_)
-/-- Frame offsets for the register arguments, followed by the caller's
-stack arguments. The enclosing frame occupies 168 bytes. -/
+/-- Frame offsets for the saved register arguments and private copies of
+the caller's stack arguments. The private frame occupies 272 bytes. -/
def passOffset : Nat := 72
def saltLenOffset : Nat := 80
def saltOffset : Nat := 88
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitialBody.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitialBody.lean
new file mode 100644
index 000000000..4aec223c7
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/InitialBody.lean
@@ -0,0 +1,13 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Initial
+import VerifiedGarbage.Impl.Argon2.X86_64.InitFill
+
+/-! Complete derivation inside its enclosing argument and register-save frame. -/
+
+namespace VG.Impl.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64
+
+def code (name : String) (hash : HPrime.Hash) : Prog isa :=
+ .seq (Initial.code hash) (InitFill.code name hash)
+
+end VG.Impl.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/Parameters.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Parameters.lean
new file mode 100644
index 000000000..47893b7b1
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/Parameters.lean
@@ -0,0 +1,19 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Divide
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Compute the rounded lane length from the normalized memory cost and lane count. -/
+
+namespace VG.Impl.Argon2.X86_64.Parameters
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def args : List Instr :=
+ [.mov .rdi (.mem (at_ .rbp 176)), .mov .rsi (.mem (at_ .rbp 184)),
+ .alu .add .rsi (.reg .rsi), .alu .add .rsi (.reg .rsi)]
+
+def finish : List Instr := [.mov .r13 (.reg .r9), .alu .add .r13 (.reg .r13), .alu .add .r13 (.reg .r13)]
+
+def code : Prog isa := .seq (.block args) (.seq Divide.code (.block finish))
+
+end VG.Impl.Argon2.X86_64.Parameters
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/RandomSource.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/RandomSource.lean
new file mode 100644
index 000000000..e4ccc67f0
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/RandomSource.lean
@@ -0,0 +1,17 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressMode
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressCache
+import VerifiedGarbage.Impl.Argon2.X86_64.DependentWord
+
+/-! Dispatch the filling random word using the public segment addressing mode. -/
+
+namespace VG.Impl.Argon2.X86_64.RandomSource
+
+open VG.X86_64
+
+def test : List Instr := [.alu .cmp .r10 (.imm 0)]
+
+def prepare : Prog isa := .seq AddressMode.code (.block test)
+
+def code : Prog isa := .seq prepare (.ite .e DependentWord.code AddressCache.code)
+
+end VG.Impl.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceBlock.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceBlock.lean
new file mode 100644
index 000000000..1fb7532e9
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceBlock.lean
@@ -0,0 +1,11 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillWrite
+
+/-! XOR a last-lane block at `rsi` into the accumulator at `rdi`. -/
+
+namespace VG.Impl.Argon2.X86_64.ReduceBlock
+
+open VG.X86_64
+
+def code : Prog isa := .block (FillWrite.words true 128)
+
+end VG.Impl.Argon2.X86_64.ReduceBlock
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLane.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLane.lean
new file mode 100644
index 000000000..11f6371ff
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLane.lean
@@ -0,0 +1,12 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReducePointers
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceBlock
+
+/-! Accumulate one lane's last block into matrix block zero. -/
+
+namespace VG.Impl.Argon2.X86_64.ReduceLane
+
+open VG.X86_64
+
+def code : Prog isa := .seq ReducePointers.code ReduceBlock.code
+
+end VG.Impl.Argon2.X86_64.ReduceLane
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLanes.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLanes.lean
new file mode 100644
index 000000000..e1abc56ce
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReduceLanes.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceLane
+import VerifiedGarbage.Impl.Argon2.X86_64.FillLanes
+
+/-! Visit each public lane once to reduce its last block. -/
+
+namespace VG.Impl.Argon2.X86_64.ReduceLanes
+
+open VG.X86_64
+
+def advance : List Instr := FillLanes.advance
+
+def body : Prog isa := .seq ReduceLane.code (.block advance)
+
+def loop : Prog isa := .loop body .b
+
+end VG.Impl.Argon2.X86_64.ReduceLanes
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReducePointers.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReducePointers.lean
new file mode 100644
index 000000000..e86b85988
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReducePointers.lean
@@ -0,0 +1,19 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.BlockAddress
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Select block zero and the last block of the current public lane. -/
+
+namespace VG.Impl.Argon2.X86_64.ReducePointers
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def setup : List Instr :=
+ [.mov .r8 (.mem (at_ .rbp 232)), .mov .rax (.reg .rbx),
+ .mov .rcx (.reg .r12), .alu .sub .rcx (.imm 1)]
+
+def finish : List Instr := [.mov .rsi (.reg .rax), .mov .rdi (.reg .r8)]
+
+def code : Prog isa := .seq (.block setup) (.seq BlockAddress.code (.block finish))
+
+end VG.Impl.Argon2.X86_64.ReducePointers
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReductionInit.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReductionInit.lean
new file mode 100644
index 000000000..257454699
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReductionInit.lean
@@ -0,0 +1,15 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ClearBlock
+import VerifiedGarbage.Impl.Argon2.X86_64.Compress
+
+/-! Begin the final reduction at lane zero with a zero accumulator in matrix block zero. -/
+
+namespace VG.Impl.Argon2.X86_64.ReductionInit
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def setup : List Instr := [.mov .rdi (.mem (at_ .rbp 232)), .mov .rbx (.imm 0)]
+
+def code : Prog isa := .seq (.block setup) ClearBlock.code
+
+end VG.Impl.Argon2.X86_64.ReductionInit
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReferenceMap.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReferenceMap.lean
new file mode 100644
index 000000000..512a4e087
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/ReferenceMap.lean
@@ -0,0 +1,44 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceLane
+import VerifiedGarbage.Impl.Argon2.X86_64.FirstLane
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceStart
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceCount
+import VerifiedGarbage.Impl.Argon2.X86_64.Relative
+import VerifiedGarbage.Impl.Argon2.X86_64.Wrap
+
+/-! Complete mapping of J₁ and J₂ to a reference lane and column.
+
+`rdi` contains the random word and `rsi` the lane count. The current
+lane is in `rbx`, lane and segment lengths in `r12` and `r13`, slice and
+index in `r14` and `r15`. The pass counter is at the frame base `rbp`:
+H₀'s first word is reused after memory initialization. `r9` and `rdi`
+receive the reference lane and column. The input word is retained in `r11`.
+-/
+
+namespace VG.Impl.Argon2.X86_64.ReferenceMap
+
+open VG.X86_64
+
+def loadPass : List Instr := [.mov .r9 (.mem { base := .rbp })]
+
+def laneArgs : List Instr := [.mov .rdi (.reg .r8), .mov .rsi (.reg .rbx)]
+
+def relativeArgs : List Instr := [
+ .mov .r9 (.reg .rdi), .mov .rdi (.reg .r11), .mov .rsi (.reg .r8)]
+
+def wrapArgs : List Instr := [
+ .mov .rdi (.reg .rax), .alu .add .rdi (.reg .r10), .mov .rsi (.reg .r12)]
+
+def chooseLane : Prog isa :=
+ .seq ReferenceLane.code (.seq (.block loadPass) FirstLane.code)
+
+def prepareLanes : Prog isa := .seq chooseLane (.block laneArgs)
+
+def window : Prog isa := .seq ReferenceStart.code ReferenceCount.code
+
+def relative : Prog isa := .seq (.block relativeArgs) Relative.code
+
+def finish : Prog isa := .seq (.block wrapArgs) Wrap.code
+
+def code : Prog isa := .seq prepareLanes (.seq window (.seq relative finish))
+
+end VG.Impl.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Impl/Argon2/X86_64/SegmentSetup.lean b/lean/VerifiedGarbage/Impl/Argon2/X86_64/SegmentSetup.lean
new file mode 100644
index 000000000..365bc7360
--- /dev/null
+++ b/lean/VerifiedGarbage/Impl/Argon2/X86_64/SegmentSetup.lean
@@ -0,0 +1,25 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSegment
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressCache
+
+/-! Reset the address cache per segment and skip the two initialized cells. -/
+
+namespace VG.Impl.Argon2.X86_64.SegmentSetup
+
+open VG.X86_64
+open VG.Impl.Argon2.X86_64 (at_)
+
+def reset : Prog isa := .seq (.block [.mov .rax (.imm 0)]) (.block AddressCache.save)
+
+def first : List Instr := [
+ .mov .rcx (.mem (at_ .rbp 0)), .alu .or .rcx (.reg .r14), .alu .cmp .rcx (.imm 0)]
+
+def index : Prog isa := .seq (.block first)
+ (.ite .e (.block [.mov .r15 (.imm 2)]) (.block [.mov .r15 (.imm 0)]))
+
+def check : List Instr := [.alu .cmp .r15 (.reg .r13)]
+
+def prepare : Prog isa := .seq reset index
+
+def code : Prog isa := .seq prepare (.seq (.block check) (.ite .b FillSegment.loop (.block [])))
+
+end VG.Impl.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/AddressInput.lean b/lean/VerifiedGarbage/Proof/Argon2/AddressInput.lean
new file mode 100644
index 000000000..ef01d17f1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/AddressInput.lean
@@ -0,0 +1,19 @@
+import VerifiedGarbage.Spec.Argon2
+
+/-! The input block of the reviewed independent-address specification. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def addressInput (p : Params) (pass lane slice counter : Nat) : Block :=
+ zeroBlock |>.set 0 (BitVec.ofNat 64 pass) |>.set 1 (BitVec.ofNat 64 lane)
+ |>.set 2 (BitVec.ofNat 64 slice) |>.set 3 (BitVec.ofNat 64 p.blocks)
+ |>.set 4 (BitVec.ofNat 64 p.passes) |>.set 5 (BitVec.ofNat 64 p.variant.code)
+ |>.set 6 (BitVec.ofNat 64 counter)
+
+theorem addressBlock_eq (p : Params) (pass lane slice counter : Nat) :
+ addressBlock p pass lane slice counter =
+ compress zeroBlock (compress zeroBlock (addressInput p pass lane slice counter)) := rfl
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/FillPositions.lean b/lean/VerifiedGarbage/Proof/Argon2/FillPositions.lean
new file mode 100644
index 000000000..850534bf1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/FillPositions.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Argon2.Dimensions
+import VerifiedGarbage.Proof.Framework.Offset
+
+/-! Bounds for every block address used by the filling loop. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+theorem previous_column_lt (p : Params) (hl : 0 < p.lanes)
+ (hm : 8 * p.lanes ≤ p.memory) (column : Nat) :
+ (column + p.laneLen - 1) % p.laneLen < p.laneLen := by
+ have seg := segmentLen_ge_two p hl hm
+ have lanes := laneLen_segments p hl
+ exact Nat.mod_lt _ (by omega)
+
+theorem cell_bytes (p : Params) (hl : 0 < p.lanes) {lane column : Nat}
+ (hlane : lane < p.lanes) (hcolumn : column < p.laneLen) :
+ (lane * p.laneLen + column) * 1024 + 1024 ≤ p.blocks * 1024 := by
+ have cell := cell_lt p hl hlane hcolumn
+ have scaled := Nat.mul_le_mul_right 1024 (show lane * p.laneLen + column + 1 ≤ p.blocks by omega)
+ simpa only [Nat.add_mul, Nat.one_mul] using scaled
+
+theorem current_cell_lt (p : Params) (hl : 0 < p.lanes) {lane slice index : Nat}
+ (hlane : lane < p.lanes) (hslice : slice < 4) (hindex : index < p.segmentLen) :
+ lane * p.laneLen + (slice * p.segmentLen + index) < p.blocks :=
+ cell_lt p hl hlane (column_lt p hl hslice hindex)
+
+theorem previous_cell_lt (p : Params) (hl : 0 < p.lanes)
+ (hm : 8 * p.lanes ≤ p.memory) {lane column : Nat} (hlane : lane < p.lanes) :
+ lane * p.laneLen + ((column + p.laneLen - 1) % p.laneLen) < p.blocks :=
+ cell_lt p hl hlane (previous_column_lt p hl hm column)
+
+theorem reference_cell_lt (p : Params) (hl : 0 < p.lanes)
+ (hm : 8 * p.lanes ≤ p.memory) (pass lane slice index : Nat) (random : Word)
+ (hlane : lane < p.lanes) :
+ let ref := reference p pass lane slice index random
+ ref.1 * p.laneLen + ref.2 < p.blocks := by
+ obtain ⟨laneBound, columnBound⟩ := reference_bounds p hl hm pass lane slice index random hlane
+ exact cell_lt p hl laneBound columnBound
+
+theorem cell_contains (base : VG.Addr) (p : Params) (hl : 0 < p.lanes)
+ (hm : p.memory < 2 ^ 32) {lane column : Nat}
+ (hlane : lane < p.lanes) (hcolumn : column < p.laneLen) :
+ (⟨base, p.blocks * 1024⟩ : VG.Region).Contains
+ (base + BitVec.ofNat 64 ((lane * p.laneLen + column) * 1024)) 1024 := by
+ have bytes := cell_bytes p hl hlane hcolumn
+ have blocks : p.blocks < 2 ^ 32 := Nat.lt_of_le_of_lt (blocks_le_memory p) hm
+ have total : p.blocks * 1024 < 2 ^ 64 :=
+ Nat.lt_trans (Nat.mul_lt_mul_of_pos_right blocks (by decide : 0 < 1024)) (by decide +kernel)
+ exact VG.Offset.contains_base base
+ (d := (lane * p.laneLen + column) * 1024) (n := 1024) (k := p.blocks * 1024)
+ bytes (Nat.lt_of_le_of_lt (Nat.le_trans (Nat.le_add_right _ _) bytes) total)
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/FillStep.lean b/lean/VerifiedGarbage/Proof/Argon2/FillStep.lean
new file mode 100644
index 000000000..cf2b7137c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/FillStep.lean
@@ -0,0 +1,41 @@
+import VerifiedGarbage.Spec.Argon2
+
+/-! Expose the reviewed filling step's random word, matrix update and leakage log. -/
+
+namespace VG.Proof.Argon2.FillStep
+
+open VG.Spec.Argon2
+
+def random (p : Params) (pass lane slice index : Nat) (blocks : Array Block) : Word :=
+ if independent p pass slice then
+ (addressBlock p pass lane slice (index / 128 + 1))[index % 128]'(Nat.mod_lt _ (by decide))
+ else
+ (blocks[lane * p.laneLen + (slice * p.segmentLen + index + p.laneLen - 1) % p.laneLen]?.getD zeroBlock)[0]
+
+def update (p : Params) (pass lane slice index : Nat) (blocks : Array Block) (word : Word) : Array Block :=
+ let column := slice * p.segmentLen + index
+ let current := lane * p.laneLen + column
+ let prev := blocks[lane * p.laneLen + (column + p.laneLen - 1) % p.laneLen]?.getD zeroBlock
+ let ref := reference p pass lane slice index word
+ let other := blocks[ref.1 * p.laneLen + ref.2]?.getD zeroBlock
+ let next := compress prev other
+ blocks.set! current (if pass = 0 then next else xorBlock next (blocks[current]?.getD zeroBlock))
+
+theorem not_skipped (pass slice index : Nat) (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index) :
+ ¬(pass = 0 ∧ slice = 0 ∧ index < 2) := by omega
+
+theorem memory (p : Params) (pass lane slice index : Nat) (s : FillState)
+ (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index) :
+ (fillBlock p pass slice lane index s).memory =
+ update p pass lane slice index s.memory (random p pass lane slice index s.memory) := by
+ rw [fillBlock, ite_eq_right (not_skipped pass slice index active)]
+ rfl
+
+theorem indices (p : Params) (pass lane slice index : Nat) (s : FillState)
+ (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index) :
+ (fillBlock p pass slice lane index s).indices = if independent p pass slice then s.indices
+ else reference p pass lane slice index (random p pass lane slice index s.memory) :: s.indices := by
+ rw [fillBlock, ite_eq_right (not_skipped pass slice index active)]
+ rfl
+
+end VG.Proof.Argon2.FillStep
diff --git a/lean/VerifiedGarbage/Proof/Argon2/FinalReduction.lean b/lean/VerifiedGarbage/Proof/Argon2/FinalReduction.lean
new file mode 100644
index 000000000..aee7f3a1a
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/FinalReduction.lean
@@ -0,0 +1,45 @@
+import VerifiedGarbage.Proof.Argon2.Matrix
+import VerifiedGarbage.Proof.Argon2.Dimensions
+
+/-! The final lane reduction, without changing the reviewed finish specification. -/
+
+namespace VG.Proof.Argon2
+
+open VG VG.Spec.Argon2
+
+def lastIndex (p : Params) (lane : Nat) : Nat := (lane + 1) * p.laneLen - 1
+
+def reduction (p : Params) (memory : Array Block) (start count : Nat) (acc : Block) : Block :=
+ (List.range' start count).foldl (fun b lane => xorBlock b (memory[lastIndex p lane]?.getD zeroBlock)) acc
+
+theorem reduction_zero (p : Params) (memory : Array Block) (start : Nat) (acc : Block) :
+ reduction p memory start 0 acc = acc := rfl
+
+theorem reduction_succ (p : Params) (memory : Array Block) (start count : Nat) (acc : Block) :
+ reduction p memory start (count + 1) acc =
+ reduction p memory (start + 1) count (xorBlock acc (memory[lastIndex p start]?.getD zeroBlock)) := by
+ simp only [reduction, List.range'_succ, List.foldl_cons]
+
+theorem finish_reduction (p : Params) (memory : Array Block) :
+ finish p memory = hPrime p.tagLen (serialize (reduction p memory 0 p.lanes zeroBlock)) := by
+ rw [finish, reduction, List.range_eq_range']
+ rfl
+
+theorem lastIndex_bounds (p : Params) (positive : 0 < p.lanes) (minimum : 2 ≤ p.segmentLen)
+ (lane : Nat) (active : lane < p.lanes) : 0 < lastIndex p lane ∧ lastIndex p lane < p.blocks := by
+ have q : 8 ≤ p.laneLen := by
+ have eq := laneLen_segments p positive
+ omega
+ have total := blocks_lanes p positive
+ have product : (lane + 1) * p.laneLen ≤ p.lanes * p.laneLen := Nat.mul_le_mul_right _ (by omega)
+ have low : p.laneLen ≤ (lane + 1) * p.laneLen := by
+ simpa only [Nat.one_mul] using Nat.mul_le_mul_right p.laneLen (show 1 ≤ lane + 1 by omega)
+ unfold lastIndex
+ omega
+
+theorem xorBlock_comm (a b : Block) : xorBlock a b = xorBlock b a := by
+ apply Vector.ext
+ intro i hi
+ simp only [xorBlock, Vector.getElem_zipWith, BitVec.xor_comm]
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Iterations.lean b/lean/VerifiedGarbage/Proof/Argon2/Iterations.lean
new file mode 100644
index 000000000..2a72cbb32
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Iterations.lean
@@ -0,0 +1,24 @@
+import VerifiedGarbage.Spec.Argon2
+
+/-! Pass folds used by the outer filling-loop invariant. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def iterations (p : Params) (start count : Nat) (state : FillState) : FillState :=
+ (List.range' start count).foldl (fillPass p) state
+
+theorem iterations_zero (p : Params) (start : Nat) (state : FillState) : iterations p start 0 state = state := rfl
+
+theorem iterations_succ (p : Params) (start count : Nat) (state : FillState) :
+ iterations p start (count + 1) state = iterations p (start + 1) count (fillPass p state start) := by
+ unfold iterations
+ rw [List.range'_succ, List.foldl_cons]
+
+theorem iterations_fill (p : Params) (password salt secret ad : List Byte) :
+ iterations p 0 p.passes (initMemory p (initialHash p password salt secret ad)) = fill p password salt secret ad := by
+ unfold iterations fill
+ rw [List.range_eq_range']
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/IterationsIndices.lean b/lean/VerifiedGarbage/Proof/Argon2/IterationsIndices.lean
new file mode 100644
index 000000000..1957da1a9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/IterationsIndices.lean
@@ -0,0 +1,41 @@
+import VerifiedGarbage.Proof.Argon2.Iterations
+import VerifiedGarbage.Proof.Argon2.SlicesIndices
+
+/-! Recover each pass's reviewed reference log from the complete filling log. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def passReferences (p : Params) (pass : Nat) : Nat := slicesReferences p pass 0 4
+
+def iterationsReferences (p : Params) (start : Nat) : Nat → Nat
+ | 0 => 0
+ | n + 1 => passReferences p start + iterationsReferences p (start + 1) n
+
+theorem pass_indices_drop (p : Params) (pass : Nat) (state : FillState) (minimum : 2 ≤ p.segmentLen) :
+ (fillPass p state pass).indices.drop (passReferences p pass) = state.indices := by
+ rw [← slices_pass p pass state]
+ exact slices_indices_drop p pass 0 4 state minimum
+
+theorem iterations_indices_drop (p : Params) (start count : Nat) (state : FillState) (minimum : 2 ≤ p.segmentLen) :
+ (iterations p start count state).indices.drop (iterationsReferences p start count) = state.indices := by
+ induction count generalizing start state with
+ | zero => rfl
+ | succ n ih =>
+ rw [iterations_succ, iterationsReferences,
+ show passReferences p start + iterationsReferences p (start + 1) n =
+ iterationsReferences p (start + 1) n + passReferences p start from Nat.add_comm _ _,
+ ← List.drop_drop, ih, pass_indices_drop p start state minimum]
+
+theorem iterations_first_pass (p : Params) (start count : Nat) (leftState rightState : FillState)
+ (minimum : 2 ≤ p.segmentLen)
+ (indices : (iterations p start (count + 1) leftState).indices =
+ (iterations p start (count + 1) rightState).indices) :
+ (fillPass p leftState start).indices = (fillPass p rightState start).indices := by
+ have dropped := congrArg (List.drop (iterationsReferences p (start + 1) count)) indices
+ rw [iterations_succ, iterations_succ, iterations_indices_drop p (start + 1) count _ minimum,
+ iterations_indices_drop p (start + 1) count _ minimum] at dropped
+ exact dropped
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Lanes.lean b/lean/VerifiedGarbage/Proof/Argon2/Lanes.lean
new file mode 100644
index 000000000..92ff0eb1b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Lanes.lean
@@ -0,0 +1,21 @@
+import VerifiedGarbage.Proof.Argon2.Segment
+
+/-! Lane folds used by the public filling loops. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def lanes (p : Params) (pass slice start count : Nat) (state : FillState) : FillState :=
+ (List.range' start count).foldl (fun state lane => segment p pass lane slice 0 p.segmentLen state) state
+
+theorem lanes_zero (p : Params) (pass slice start : Nat) (state : FillState) :
+ lanes p pass slice start 0 state = state := rfl
+
+theorem lanes_succ (p : Params) (pass slice start count : Nat) (state : FillState) :
+ lanes p pass slice start (count + 1) state =
+ lanes p pass slice (start + 1) count (segment p pass start slice 0 p.segmentLen state) := by
+ unfold lanes
+ rw [List.range'_succ, List.foldl_cons]
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/LanesIndices.lean b/lean/VerifiedGarbage/Proof/Argon2/LanesIndices.lean
new file mode 100644
index 000000000..f5f9d2070
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/LanesIndices.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Proof.Argon2.Lanes
+import VerifiedGarbage.Proof.Argon2.SegmentIndices
+import VerifiedGarbage.Proof.Argon2.SegmentStart
+
+/-! Recover each segment's reference log from the complete lane fold. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def segmentReferences (p : Params) (pass slice : Nat) : Nat :=
+ if independent p pass slice then 0 else p.segmentLen - segmentStart pass slice
+
+theorem fillBlock_independent_indices (p : Params) (pass lane slice index : Nat) (state : FillState)
+ (mode : independent p pass slice = true) : (fillBlock p pass slice lane index state).indices = state.indices := by
+ unfold fillBlock
+ split
+ · rfl
+ · simp only [mode, ite_true]
+
+theorem segment_independent_indices (p : Params) (pass lane slice start count : Nat) (state : FillState)
+ (mode : independent p pass slice = true) : (segment p pass lane slice start count state).indices = state.indices := by
+ induction count generalizing start state with
+ | zero => rfl
+ | succ n ih =>
+ rw [segment_succ, ih, fillBlock_independent_indices p pass lane slice start state mode]
+
+theorem segment_references_drop (p : Params) (pass lane slice : Nat) (state : FillState)
+ (minimum : 2 ≤ p.segmentLen) :
+ (segment p pass lane slice 0 p.segmentLen state).indices.drop (segmentReferences p pass slice) = state.indices := by
+ cases mode : independent p pass slice
+ · rw [segment_start p pass lane slice state minimum]
+ change (segment p pass lane slice (segmentStart pass slice) (p.segmentLen - segmentStart pass slice) state).indices.drop
+ (if independent p pass slice then 0 else p.segmentLen - segmentStart pass slice) = state.indices
+ simp only [mode, Bool.false_eq_true, ite_false]
+ apply segment_indices_drop _ _ _ _ _ _ _ _ mode
+ unfold segmentStart; split <;> omega
+ · rw [segment_independent_indices p pass lane slice 0 p.segmentLen state mode]
+ simp only [segmentReferences, mode, ite_true, List.drop_zero]
+
+theorem lanes_indices_drop (p : Params) (pass slice start count : Nat) (state : FillState)
+ (minimum : 2 ≤ p.segmentLen) :
+ (lanes p pass slice start count state).indices.drop (count * segmentReferences p pass slice) = state.indices := by
+ induction count generalizing start state with
+ | zero => rw [Nat.zero_mul, lanes_zero, List.drop_zero]
+ | succ n ih =>
+ rw [lanes_succ, Nat.add_mul, Nat.one_mul, ← List.drop_drop,
+ ih, segment_references_drop p pass start slice state minimum]
+
+theorem lanes_first_segment (p : Params) (pass slice start count : Nat) (leftState rightState : FillState)
+ (minimum : 2 ≤ p.segmentLen)
+ (indices : (lanes p pass slice start (count + 1) leftState).indices =
+ (lanes p pass slice start (count + 1) rightState).indices) :
+ (segment p pass start slice 0 p.segmentLen leftState).indices =
+ (segment p pass start slice 0 p.segmentLen rightState).indices := by
+ have dropped := congrArg (List.drop (count * segmentReferences p pass slice)) indices
+ rw [lanes_succ, lanes_succ, lanes_indices_drop p pass slice (start + 1) count _ minimum,
+ lanes_indices_drop p pass slice (start + 1) count _ minimum] at dropped
+ exact dropped
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Matrix.lean b/lean/VerifiedGarbage/Proof/Argon2/Matrix.lean
new file mode 100644
index 000000000..d2a7daea1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Matrix.lean
@@ -0,0 +1,39 @@
+import VerifiedGarbage.Spec.Argon2.Contract
+import VerifiedGarbage.Proof.Framework.Offset
+
+/-! Relate the lane-major assembly allocation to the specification's block array. -/
+
+namespace VG.Proof.Argon2
+
+open VG VG.Spec.Argon2
+
+def matrixCell (base : Addr) (k : Nat) : Addr := base + BitVec.ofNat 64 (k * 1024)
+
+structure Represents (m : Mem) (base : Addr) (n : Nat) (blocks : Array Block) : Prop where
+ size : blocks.size = n
+ block : ∀ k < n, blockAt m (matrixCell base k) = blocks[k]?.getD zeroBlock
+
+theorem Represents.update {m m' : Mem} {base : Addr} {n : Nat} {blocks : Array Block}
+ (h : Represents m base n blocks) (k : Nat) (hk : k < n) (value : Block)
+ (written : blockAt m' (matrixCell base k) = value)
+ (kept : ∀ j < n, j ≠ k → blockAt m' (matrixCell base j) = blockAt m (matrixCell base j)) :
+ Represents m' base n (blocks.set! k value) := by
+ refine ⟨(Array.size_set! _ _ _).trans h.size, ?_⟩
+ intro j hj
+ rw [Array.set!_eq_setIfInBounds, Array.getElem?_setIfInBounds]
+ by_cases equal : k = j
+ · rw [ite_eq_left equal, ite_eq_left (by rw [h.size]; exact hk), Option.getD_some]
+ rw [← equal]; exact written
+ · rw [ite_eq_right equal]
+ exact (kept j hj (Ne.symm equal)).trans (h.block j hj)
+
+theorem matrixCell_sub (base : Addr) (n k : Nat) (hk : k < n) :
+ Region.Sub ⟨matrixCell base k, 1024⟩ ⟨base, n * 1024⟩ :=
+ Offset.sub_base base (by omega)
+
+theorem matrixCell_disjoint (base : Addr) (n i j : Nat) (bound : n * 1024 < 2 ^ 64)
+ (hi : i < n) (hj : j < n) (different : i ≠ j) :
+ (⟨matrixCell base i, 1024⟩ : Region).Disjoint ⟨matrixCell base j, 1024⟩ :=
+ Offset.disjoint base (by omega) (by omega) (by omega)
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/References.lean b/lean/VerifiedGarbage/Proof/Argon2/References.lean
new file mode 100644
index 000000000..b2b3d6241
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/References.lean
@@ -0,0 +1,86 @@
+import VerifiedGarbage.Proof.Argon2.Iterations
+import VerifiedGarbage.Proof.Argon2.FillStep
+
+/-! The reviewed flattened reference log determines every lane/column pair. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def Columns (q : Nat) (s : FillState) : Prop := ∀ ref ∈ s.indices, ref.2 < q
+
+theorem fold_columns {α : Type} (q : Nat) (f : FillState → α → FillState)
+ (step : ∀ s a, Columns q s → Columns q (f s a)) (xs : List α) (s : FillState) (h : Columns q s) :
+ Columns q (xs.foldl f s) := by
+ induction xs generalizing s with
+ | nil => exact h
+ | cons x xs ih => exact ih (f s x) (step s x h)
+
+theorem fillBlock_columns (p : Params) (positive : 0 < p.laneLen)
+ (pass slice lane index : Nat) (s : FillState) (h : Columns p.laneLen s) :
+ Columns p.laneLen (fillBlock p pass slice lane index s) := by
+ by_cases skipped : pass = 0 ∧ slice = 0 ∧ index < 2
+ · rw [fillBlock, ite_eq_left skipped]; exact h
+ · unfold Columns
+ rw [FillStep.indices p pass lane slice index s (by omega)]
+ split
+ · exact h
+ · intro ref hr
+ simp only [List.mem_cons] at hr
+ rcases hr with rfl | hr
+ · change _ % p.laneLen < p.laneLen
+ exact Nat.mod_lt _ positive
+ · exact h ref hr
+
+theorem fillPass_columns (p : Params) (positive : 0 < p.laneLen) (s : FillState) (pass : Nat)
+ (h : Columns p.laneLen s) : Columns p.laneLen (fillPass p s pass) := by
+ unfold fillPass
+ apply fold_columns
+ · intro s slice hs
+ apply fold_columns
+ · intro s lane hs
+ exact fold_columns _ _ (fun s index hs => fillBlock_columns p positive pass slice lane index s hs) _ s hs
+ · exact hs
+ · exact h
+
+theorem fill_columns (p : Params) (positive : 0 < p.laneLen) (password salt secret ad : List Byte) :
+ Columns p.laneLen (fill p password salt secret ad) := by
+ unfold fill
+ apply fold_columns _ _ (fun s pass hs => fillPass_columns p positive s pass hs)
+ intro ref hr
+ exact False.elim (List.not_mem_nil hr)
+
+def flattenRef (q : Nat) (ref : Nat × Nat) : Nat := ref.1 * q + ref.2
+
+def decodeRef (q n : Nat) : Nat × Nat := (n / q, n % q)
+
+theorem decode_flatten (q : Nat) (positive : 0 < q) (ref : Nat × Nat) (bound : ref.2 < q) :
+ decodeRef q (flattenRef q ref) = ref := by
+ unfold decodeRef flattenRef
+ rw [Nat.mul_comm ref.1 q, Nat.mul_add_div positive,
+ Nat.div_eq_of_lt bound, Nat.add_zero, Nat.mul_add_mod_self_left, Nat.mod_eq_of_lt bound]
+
+theorem decode_list (q : Nat) (positive : 0 < q) (xs : List (Nat × Nat))
+ (bound : ∀ ref ∈ xs, ref.2 < q) : (xs.map (flattenRef q)).map (decodeRef q) = xs := by
+ induction xs with
+ | nil => rfl
+ | cons ref xs ih =>
+ simp only [List.map_cons]
+ rw [decode_flatten q positive ref (bound ref (List.mem_cons_self ..)),
+ ih (fun r hr => bound r (List.mem_cons_of_mem ref hr))]
+
+theorem references_injective (p : Params) (positive : 0 < p.laneLen)
+ (password₁ salt₁ secret₁ ad₁ password₂ salt₂ secret₂ ad₂ : List Byte)
+ (same : references p password₁ salt₁ secret₁ ad₁ = references p password₂ salt₂ secret₂ ad₂) :
+ (fill p password₁ salt₁ secret₁ ad₁).indices = (fill p password₂ salt₂ secret₂ ad₂).indices := by
+ apply List.reverse_inj.mp
+ have left := fill_columns p positive password₁ salt₁ secret₁ ad₁
+ have right := fill_columns p positive password₂ salt₂ secret₂ ad₂
+ have decoded := congrArg (List.map (decodeRef p.laneLen)) same
+ change ((fill p password₁ salt₁ secret₁ ad₁).indices.reverse.map (flattenRef p.laneLen)).map _ =
+ ((fill p password₂ salt₂ secret₂ ad₂).indices.reverse.map (flattenRef p.laneLen)).map _ at decoded
+ rw [decode_list p.laneLen positive _ (fun ref hr => left ref (List.mem_reverse.mp hr)),
+ decode_list p.laneLen positive _ (fun ref hr => right ref (List.mem_reverse.mp hr))] at decoded
+ exact decoded
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Segment.lean b/lean/VerifiedGarbage/Proof/Argon2/Segment.lean
new file mode 100644
index 000000000..5cbdfa8e4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Segment.lean
@@ -0,0 +1,27 @@
+import VerifiedGarbage.Spec.Argon2
+
+/-! Segment folds used by the filling-loop invariant. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def segment (p : Params) (pass lane slice start count : Nat) (state : FillState) : FillState :=
+ (List.range' start count).foldl (fun state index => fillBlock p pass slice lane index state) state
+
+theorem segment_zero (p : Params) (pass lane slice start : Nat) (state : FillState) :
+ segment p pass lane slice start 0 state = state := rfl
+
+theorem segment_succ (p : Params) (pass lane slice start count : Nat) (state : FillState) :
+ segment p pass lane slice start (count + 1) state =
+ segment p pass lane slice (start + 1) count (fillBlock p pass slice lane start state) := by
+ unfold segment
+ rw [List.range'_succ, List.foldl_cons]
+
+theorem segment_append (p : Params) (pass lane slice start a b : Nat) (state : FillState) :
+ segment p pass lane slice start (a + b) state =
+ segment p pass lane slice (start + a) b (segment p pass lane slice start a state) := by
+ unfold segment
+ rw [← List.range'_append_1, List.foldl_append]
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/SegmentIndices.lean b/lean/VerifiedGarbage/Proof/Argon2/SegmentIndices.lean
new file mode 100644
index 000000000..caf79489c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/SegmentIndices.lean
@@ -0,0 +1,38 @@
+import VerifiedGarbage.Proof.Argon2.Segment
+import VerifiedGarbage.Proof.Argon2.FillStep
+
+/-! The reference log exposes exactly one coordinate per dependent active cell. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+theorem segment_indices_drop (p : Params) (pass lane slice start count : Nat) (state : FillState)
+ (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ start) (dependent : independent p pass slice = false) :
+ (segment p pass lane slice start count state).indices.drop count = state.indices := by
+ induction count generalizing start state with
+ | zero => rfl
+ | succ n ih =>
+ rw [segment_succ]
+ have next : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ start + 1 := by omega
+ rw [← List.drop_drop, ih (start + 1) (fillBlock p pass slice lane start state) next]
+ rw [FillStep.indices p pass lane slice start state active]
+ simp only [dependent, Bool.false_eq_true, ite_false, List.drop_succ_cons, List.drop_zero]
+
+theorem segment_first_reference (p : Params) (pass lane slice start count : Nat)
+ (leftState rightState : FillState) (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ start)
+ (indices : (segment p pass lane slice start (count + 1) leftState).indices =
+ (segment p pass lane slice start (count + 1) rightState).indices)
+ (dependent : independent p pass slice = false) :
+ reference p pass lane slice start (FillStep.random p pass lane slice start leftState.memory) =
+ reference p pass lane slice start (FillStep.random p pass lane slice start rightState.memory) := by
+ have dropped := congrArg (List.drop count) indices
+ rw [segment_succ, segment_succ,
+ segment_indices_drop p pass lane slice (start + 1) count _ (by omega) dependent,
+ segment_indices_drop p pass lane slice (start + 1) count _ (by omega) dependent,
+ FillStep.indices p pass lane slice start leftState active,
+ FillStep.indices p pass lane slice start rightState active] at dropped
+ simp only [dependent, Bool.false_eq_true, ite_false] at dropped
+ exact (List.cons.inj dropped).1
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/SegmentStart.lean b/lean/VerifiedGarbage/Proof/Argon2/SegmentStart.lean
new file mode 100644
index 000000000..bb7d434c8
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/SegmentStart.lean
@@ -0,0 +1,26 @@
+import VerifiedGarbage.Proof.Argon2.Segment
+
+/-! The first two cells of pass zero's first segment are already initialized. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def segmentStart (pass slice : Nat) : Nat := if pass = 0 ∧ slice = 0 then 2 else 0
+
+theorem segment_first_two (p : Params) (lane : Nat) (state : FillState) :
+ segment p 0 lane 0 0 2 state = state := by
+ rw [segment_succ, segment_succ, segment_zero]
+ simp only [fillBlock, Nat.reduceAdd, and_self, Nat.reduceLT, ite_true]
+
+theorem segment_start (p : Params) (pass lane slice : Nat) (state : FillState) (minimum : 2 ≤ p.segmentLen) :
+ segment p pass lane slice 0 p.segmentLen state =
+ segment p pass lane slice (segmentStart pass slice) (p.segmentLen - segmentStart pass slice) state := by
+ by_cases first : pass = 0 ∧ slice = 0
+ · obtain ⟨rfl, rfl⟩ := first
+ have append := segment_append p 0 lane 0 0 2 (p.segmentLen - 2) state
+ rw [show 2 + (p.segmentLen - 2) = p.segmentLen by omega, segment_first_two] at append
+ exact append
+ · simp only [segmentStart, first, ite_false, Nat.sub_zero]
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Serialization.lean b/lean/VerifiedGarbage/Proof/Argon2/Serialization.lean
new file mode 100644
index 000000000..adbae7e35
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Serialization.lean
@@ -0,0 +1,29 @@
+import VerifiedGarbage.Spec.Argon2.Contract
+import VerifiedGarbage.Proof.Blake2.Stream
+
+/-! Serialize the final word block as the same 1024 bytes consumed by H′. -/
+
+namespace VG.Proof.Argon2
+
+open VG VG.Spec.Argon2
+open VG.Spec.Blake2 (bytesAt)
+
+theorem serialize_blockAt (m : Mem) (p : Addr) : serialize (blockAt m p) = bytesAt m p 1024 := by
+ have words : (blockAt m p).toList =
+ (List.range 128).map (fun j => m.readW (p + BitVec.ofNat 64 (8 * j)) 64) := by
+ rw [blockAt, Vector.toList_ofFn]
+ apply List.ext_getElem (by simp only [List.length_ofFn, List.length_map, List.length_range])
+ intro i hi _
+ simp only [List.length_ofFn] at hi
+ simp only [List.getElem_ofFn, List.getElem_map, List.getElem_range]
+ rfl
+ rw [serialize, words, List.flatMap_map]
+ have bytes := Proof.Blake2.bytesAt_words (w := 64) m p 128
+ change bytesAt m p 1024 = _ at bytes
+ rw [bytes]
+ apply congrArg List.flatten
+ apply List.map_congr_left
+ intro j _
+ exact Proof.Blake2.wordBytes_readW m _ (Or.inr rfl)
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/Slices.lean b/lean/VerifiedGarbage/Proof/Argon2/Slices.lean
new file mode 100644
index 000000000..2d8114700
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/Slices.lean
@@ -0,0 +1,23 @@
+import VerifiedGarbage.Proof.Argon2.Lanes
+
+/-! Slice folds expose the reviewed filling pass without changing its specification. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def slices (p : Params) (pass start count : Nat) (state : FillState) : FillState :=
+ (List.range' start count).foldl (fun state slice => lanes p pass slice 0 p.lanes state) state
+
+theorem slices_zero (p : Params) (pass start : Nat) (state : FillState) : slices p pass start 0 state = state := rfl
+
+theorem slices_succ (p : Params) (pass start count : Nat) (state : FillState) :
+ slices p pass start (count + 1) state = slices p pass (start + 1) count (lanes p pass start 0 p.lanes state) := by
+ unfold slices
+ rw [List.range'_succ, List.foldl_cons]
+
+theorem slices_pass (p : Params) (pass : Nat) (state : FillState) : slices p pass 0 4 state = fillPass p state pass := by
+ unfold slices lanes segment fillPass
+ simp only [List.range_eq_range']
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/SlicesIndices.lean b/lean/VerifiedGarbage/Proof/Argon2/SlicesIndices.lean
new file mode 100644
index 000000000..319e38d2b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/SlicesIndices.lean
@@ -0,0 +1,38 @@
+import VerifiedGarbage.Proof.Argon2.Slices
+import VerifiedGarbage.Proof.Argon2.LanesIndices
+
+/-! Reference-log suffixes span slices with different public addressing modes. -/
+
+namespace VG.Proof.Argon2
+
+open VG.Spec.Argon2
+
+def sliceReferences (p : Params) (pass slice : Nat) : Nat := p.lanes * segmentReferences p pass slice
+
+def slicesReferences (p : Params) (pass start : Nat) : Nat → Nat
+ | 0 => 0
+ | n + 1 => sliceReferences p pass start + slicesReferences p pass (start + 1) n
+
+theorem slices_indices_drop (p : Params) (pass start count : Nat) (state : FillState)
+ (minimum : 2 ≤ p.segmentLen) :
+ (slices p pass start count state).indices.drop (slicesReferences p pass start count) = state.indices := by
+ induction count generalizing start state with
+ | zero => rfl
+ | succ n ih =>
+ rw [slices_succ, slicesReferences,
+ show sliceReferences p pass start + slicesReferences p pass (start + 1) n =
+ slicesReferences p pass (start + 1) n + sliceReferences p pass start from Nat.add_comm _ _,
+ ← List.drop_drop, ih]
+ exact lanes_indices_drop p pass start 0 p.lanes state minimum
+
+theorem slices_first_lane_fold (p : Params) (pass start count : Nat) (leftState rightState : FillState)
+ (minimum : 2 ≤ p.segmentLen)
+ (indices : (slices p pass start (count + 1) leftState).indices =
+ (slices p pass start (count + 1) rightState).indices) :
+ (lanes p pass start 0 p.lanes leftState).indices = (lanes p pass start 0 p.lanes rightState).indices := by
+ have dropped := congrArg (List.drop (slicesReferences p pass (start + 1) count)) indices
+ rw [slices_succ, slices_succ, slices_indices_drop p pass (start + 1) count _ minimum,
+ slices_indices_drop p pass (start + 1) count _ minimum] at dropped
+ exact dropped
+
+end VG.Proof.Argon2
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCache.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCache.lean
new file mode 100644
index 000000000..1ed6dcf14
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCache.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheSelect
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheWord
+
+/-! Complete cached random-word selection against RFC 9106's address block. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCache
+
+structure Done (s t : State) (p : Params) (pass lane slice : Nat) : Prop where
+ selected : Selected s t p pass lane slice
+ random : t.gpr .rdi =
+ (addressBlock p pass lane slice (wanted s))[(s.gpr .r15).toNat % 128]'(Nat.mod_lt _ (by decide))
+
+theorem code_ok (p : Params) (pass lane slice old : Nat) (s : State)
+ (h : Ready p pass lane slice old s) :
+ WP isa code s (Done s · p pass lane slice) := by
+ unfold code
+ refine WP.seq ((selected_ok p pass lane slice old s h).mono ?_)
+ intro a selected
+ refine (word_ok a selected.layout).mono ?_
+ rintro t ⟨random, keeps⟩
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r := by
+ intro r hr
+ have ne : r ∉ [Reg.rcx, .rax, .rdi] := by
+ 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 (keeps.regs r ne).trans (selected.regs r hr)
+ have bp := keeps.regs .rbp (by decide)
+ have sp := keeps.regs .rsp (by decide)
+ have work' : AddressCalls.work t = AddressCalls.work a := by
+ unfold AddressCalls.work; rw [bp, keeps.mem]
+ have layout : AddressCalls.Ready t := by
+ constructor
+ · rw [keeps.rd, keeps.wr, bp]; exact selected.layout.frameRead
+ · rw [work', keeps.wr]; exact selected.layout.workWrite
+ · rw [bp, work']; exact selected.layout.frameWork
+ · rw [bp, sp]; exact selected.layout.frameStack
+ · rw [sp, work']; exact selected.layout.stackWork
+ refine ⟨⟨?_, layout, work'.trans selected.work_eq, regs,
+ keeps.rd.trans selected.rd, keeps.wr.trans selected.wr, ?_,
+ keeps.mxcsr.trans selected.mxcsr, ?_⟩, ?_⟩
+ · rw [keeps.mem]; exact selected.block
+ · rw [keeps.mem]; exact selected.frame
+ · rw [bp, keeps.mem]; exact selected.counterWord
+ · rw [selected.work_eq, selected.regs .r15 (by simp [calleeSaved]), selected.block] at random
+ exact random
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheInvariant.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheInvariant.lean
new file mode 100644
index 000000000..19fdec209
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheInvariant.lean
@@ -0,0 +1,87 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheState
+
+/-! Cache validity does not depend on the current index, so advancing an index
+retains it. Counter zero requires no cached contents; every other counter
+identifies its specified independent-address block.
+-/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Invariant (p : Params) (pass lane slice old : Nat) (s : State) : Prop where
+ layout : AddressCalls.Ready s
+ reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ write : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ words : AddressHeader.Words p pass lane slice old s
+ bound : old < 2 ^ 64
+ cached : old = 0 ∨ blockAt s.mem (off (AddressCalls.work s) 6144) = addressBlock p pass lane slice old
+
+theorem wanted_bound (s : State) : wanted s < 2 ^ 64 := by
+ unfold wanted
+ have := (s.gpr .r15).isLt
+ omega
+
+theorem Invariant.ready {p : Params} {pass lane slice old : Nat} {s : State}
+ (h : Invariant p pass lane slice old s) : Ready p pass lane slice old s := by
+ refine ⟨h.layout, h.reads, h.write, h.words, ?_⟩
+ intro same
+ have word : BitVec.ofNat 64 (wanted s) = BitVec.ofNat 64 old := by
+ unfold wanted
+ rw [← counter_nat]; exact same.trans h.words.counterWord
+ have equal := (ReferenceMap.word_eq _ _ (wanted_bound s) h.bound).mp word
+ rcases h.cached with zero | cached
+ · exfalso
+ exact counter_ne_zero _ (same.trans (h.words.counterWord.trans (by rw [zero]; rfl)))
+ · rw [← equal] at cached
+ exact cached
+
+theorem Selected.invariant {s t : State} {p : Params} {pass lane slice old : Nat}
+ (ready : Ready p pass lane slice old s) (h : Selected s t p pass lane slice) :
+ Invariant p pass lane slice (wanted s) t := by
+ have bp := h.regs .rbp (by simp [calleeSaved])
+ refine ⟨h.layout, ?_, ?_, h.words ready, wanted_bound s, Or.inr ?_⟩
+ · rw [h.rd, h.wr, bp]; exact ready.reads
+ · rw [h.wr, bp]; exact ready.write
+ · rw [h.work_eq]; exact h.block
+
+theorem Invariant.zero {p : Params} {pass lane slice : Nat} {s : State}
+ (h : Ready p pass lane slice 0 s) : Invariant p pass lane slice 0 s :=
+ ⟨h.layout, h.reads, h.write, h.words, by decide, Or.inl rfl⟩
+
+theorem Invariant.of_state {p : Params} {pass lane slice old : Nat} {s t : State}
+ (h : Invariant p pass lane slice old s)
+ (regs : ∀ r ∈ [Reg.rsp, .rbp, .rbx, .r14], t.gpr r = s.gpr r)
+ (mem : t.mem = s.mem) (rd : t.rd = s.rd) (wr : t.wr = s.wr) :
+ Invariant p pass lane slice old t := by
+ have bp := regs .rbp (by simp)
+ have sp := regs .rsp (by simp)
+ have work : AddressCalls.work t = AddressCalls.work s := by
+ unfold AddressCalls.work; rw [mem, bp]
+ refine ⟨?_, ?_, ?_, ?_, h.bound, ?_⟩
+ · constructor
+ · rw [rd, wr, bp]; exact h.layout.frameRead
+ · rw [wr, work]; exact h.layout.workWrite
+ · rw [bp, work]; exact h.layout.frameWork
+ · rw [bp, sp]; exact h.layout.frameStack
+ · rw [sp, work]; exact h.layout.stackWork
+ · rw [rd, wr, bp]; exact h.reads
+ · rw [wr, bp]; exact h.write
+ · exact ⟨by rw [mem, bp]; exact h.words.passWord,
+ (regs .rbx (by simp)).trans h.words.laneWord,
+ (regs .r14 (by simp)).trans h.words.sliceWord,
+ by rw [mem, bp]; exact h.words.blocksWord,
+ by rw [mem, bp]; exact h.words.passesWord,
+ by rw [mem, bp]; exact h.words.variantWord,
+ by rw [mem, bp]; exact h.words.counterWord⟩
+ · rw [mem, work]; exact h.cached
+
+theorem Invariant.of_keeps {p : Params} {pass lane slice old : Nat} {s t : State}
+ (h : Invariant p pass lane slice old s) (k : Divide.Keeps ReferenceMap.changed s t) :
+ Invariant p pass lane slice old t := by
+ apply h.of_state _ k.mem k.rd k.wr
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact k.regs _ (by decide)
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMatrix.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMatrix.lean
new file mode 100644
index 000000000..bb635e73e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMatrix.lean
@@ -0,0 +1,59 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheState
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelInvariant
+import VerifiedGarbage.Proof.Argon2.Matrix
+
+/-! Independent-address generation leaves every matrix cell intact. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Selected.filling_ready {s t : State} {p : Params} {pass lane slice index : Nat}
+ (cacheLayout : AddressCalls.Ready s) (h : FillKernel.Ready p pass lane slice index s)
+ (done : Selected s t p pass lane slice) : FillKernel.Ready p pass lane slice index t := by
+ have bp := done.regs .rbp (by simp [calleeSaved])
+ have sp := done.regs .rsp (by simp [calleeSaved])
+ have matrix' : FillKernel.matrix t = FillKernel.matrix s := done.frame_word cacheLayout 232 (by decide) (by decide)
+ have work' : FillKernel.work t = FillKernel.work s := done.frame_word cacheLayout 248 (by decide) (by decide)
+ refine ⟨?_, h.bounds, ?_, (done.frame_word cacheLayout 0 (by decide) (by decide)).trans h.passWord,
+ (done.frame_word cacheLayout 184 (by decide) (by decide)).trans h.lanesWord⟩
+ · constructor
+ · rw [done.rd, done.wr, bp]; exact h.layout.frameRead
+ · rw [done.wr, bp]; exact h.layout.frameWrite
+ · rw [matrix', done.wr]; exact h.layout.matrixWrite
+ · rw [work', done.wr]; exact h.layout.workWrite
+ · rw [matrix', work']; exact h.layout.matrixWork
+ · rw [matrix', bp]; exact h.layout.matrixFrame
+ · rw [matrix', sp]; exact h.layout.matrixStack
+ · rw [bp, work']; exact h.layout.frameWork
+ · rw [bp, sp]; exact h.layout.frameStack
+ · rw [sp, work']; exact h.layout.stackWork
+ · exact ⟨(done.regs .rbx (by simp [calleeSaved])).trans h.position.current,
+ (done.regs .r12 (by simp [calleeSaved])).trans h.position.laneLength,
+ (done.regs .r13 (by simp [calleeSaved])).trans h.position.segmentLength,
+ (done.regs .r14 (by simp [calleeSaved])).trans h.position.slice,
+ (done.regs .r15 (by simp [calleeSaved])).trans h.position.index⟩
+
+theorem Selected.represents {s t : State} {p : Params} {pass lane slice index : Nat}
+ (cacheLayout : AddressCalls.Ready s) (h : FillKernel.Ready p pass lane slice index s)
+ (matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩)
+ (done : Selected s t p pass lane slice) (blocks : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks blocks) :
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks blocks := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word cacheLayout 232 (by decide) (by decide)
+ rw [base]
+ refine ⟨represented.size, ?_⟩
+ intro k hk
+ have kept : blockAt t.mem (Proof.Argon2.matrixCell (FillKernel.matrix s) k) =
+ blockAt s.mem (Proof.Argon2.matrixCell (FillKernel.matrix s) k) := by
+ apply FillCompress.block_frame done.frame
+ intro r hr
+ simp only [writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact matrixWork.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)
+ · exact h.layout.matrixStack.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)
+ · exact (h.layout.matrixFrame.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)).sub_right
+ (Offset.sub_base _ (by decide))
+ exact kept.trans (represented.block k hk)
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMeta.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMeta.lean
new file mode 100644
index 000000000..5a2664356
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheMeta.lean
@@ -0,0 +1,77 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceStart
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressCache
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsPrepare
+
+/-! Public cache counters and indexed-word arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCache
+
+def counter (index : Addr) : Addr := (index >>> 7) + 1
+
+theorem check_ok (s : State)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 8) 8) :
+ WP isa (.block check) s fun t => t.gpr .rax = counter (s.gpr .r15) ∧
+ t.zf = decide (counter (s.gpr .r15) = s.mem.readW (off (s.gpr .rbp) 8) 64) ∧
+ Divide.Keeps [.rax] s t := by
+ apply WP.of_runBlock
+ simp only [check, counter, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ execShift, execAlu, State.load64, ea_at, hr, RegUpd.gpr_setReg, RegUpd.gpr_setFlags,
+ RegUpd.gpr_arithFlags, RegUpd.mem_setReg, RegUpd.mem_setFlags, RegUpd.mem_arithFlags,
+ RegUpd.rd_setReg, RegUpd.rd_setFlags, RegUpd.rd_arithFlags,
+ RegUpd.wr_setReg, RegUpd.wr_setFlags, RegUpd.wr_arithFlags,
+ RegUpd.zf_arithFlags, reduceCtorEq, ite_true, ite_false, and_self,
+ show 1 ≤ (7 : Nat) ∧ (7 : Nat) ≤ 63 from by decide,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_, ?_⟩
+ · apply Bool.eq_iff_iff.mpr
+ simp only [beq_iff_eq, ReferenceStart.sub_zero_iff]
+ exact ⟨fun h => decide_eq_true h, of_decide_eq_true⟩
+ · constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_setFlags, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem counter_nat (index : Addr) : counter index = BitVec.ofNat 64 (index.toNat / 128 + 1) := by
+ have shifted : index >>> 7 = BitVec.ofNat 64 (index.toNat / 128) := by
+ apply BitVec.eq_of_toNat_eq
+ rw [BitVec.toNat_ushiftRight, Nat.shiftRight_eq_div_pow, BitVec.toNat_ofNat,
+ Nat.mod_eq_of_lt (by have := index.isLt; omega)]
+ unfold counter
+ rw [shifted]
+ exact (BitVec.ofNat_add _ _).symm
+
+theorem counter_ne_zero (index : Addr) : counter index ≠ 0 := by
+ have bound : index.toNat / 128 + 1 < 2 ^ 64 := by have := index.isLt; omega
+ intro h
+ have nat := congrArg BitVec.toNat h
+ rw [counter_nat, BitVec.toNat_ofNat, Nat.mod_eq_of_lt bound] at nat
+ change index.toNat / 128 + 1 = 0 at nat
+ omega
+
+theorem wordArgs_ok (s : State)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8) :
+ WP isa (.block wordArgs) s fun t => t.gpr .rcx = AddressCalls.work s ∧
+ t.gpr .rax = s.gpr .r15 &&& 127 ∧ Divide.Keeps [.rcx, .rax] s t := by
+ apply WP.of_runBlock
+ simp only [wordArgs, AddressCalls.work, runBlock_cons, runStep_some, runBlock_nil,
+ exec, readSrc, State.load64, ea_at, hr, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, reduceCtorEq, ite_true, ite_false,
+ show BitVec.signExtend 64 (127 : BitVec 32) = (127 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem index_nat (index : Addr) : index &&& 127 = BitVec.ofNat 64 (index.toNat % 128) := by
+ apply BitVec.eq_of_toNat_eq
+ rw [BitVec.toNat_and, BitVec.toNat_ofNat, Nat.mod_eq_of_lt (by omega)]
+ exact Nat.and_two_pow_sub_one_eq_mod index.toNat 7
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSave.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSave.lean
new file mode 100644
index 000000000..f69d79251
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSave.lean
@@ -0,0 +1,65 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheMeta
+
+/-! Save the public cache counter without disturbing scratch or header fields. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCache
+
+theorem save_ok (s : State) (hw : InRegions s.wr (off (s.gpr .rbp) 8) 8) :
+ WP isa (.block save) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rbp) 8) (s.gpr .rax) ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [save, runBlock_cons, runStep_some, runBlock_nil, exec, State.store64,
+ ea_at, hw, ite_true, Option.some.injEq, exists_eq_left']
+ exact ⟨trivial, trivial, trivial, trivial, trivial⟩
+
+structure Saved (s t : State) : Prop where
+ mem : t.mem = s.mem.writeW (off (s.gpr .rbp) 8) (s.gpr .rax)
+ regs : t.gpr = s.gpr
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ ready : AddressCalls.Ready t
+ work_eq : AddressCalls.work t = AddressCalls.work s
+ frame : Frame [⟨off (s.gpr .rbp) 8, 8⟩] s.mem t.mem
+
+theorem save_ready (s : State) (h : AddressCalls.Ready s)
+ (hw : InRegions s.wr (off (s.gpr .rbp) 8) 8) : WP isa (.block save) s (Saved s) := by
+ refine (save_ok s hw).mono ?_
+ rintro t ⟨mem, regs, rd, wr, mx⟩
+ have work' : AddressCalls.work t = AddressCalls.work s := by
+ unfold AddressCalls.work
+ rw [regs, mem, Mem.readW_writeW_sep (Offset.sep _ (by decide) (by decide) (by decide)) (by decide)]
+ have ready : AddressCalls.Ready t := by
+ constructor
+ · rw [rd, wr, regs]; exact h.frameRead
+ · rw [work', wr]; exact h.workWrite
+ · rw [regs, work']; exact h.frameWork
+ · rw [regs]; exact h.frameStack
+ · rw [regs, work']; exact h.stackWork
+ refine ⟨mem, regs, rd, wr, mx, ready, work', ?_⟩
+ rw [mem]
+ exact (Frame.refl _ _).writeW (r := ⟨off (s.gpr .rbp) 8, 8⟩) (by simp) _
+ (Region.contains_self _ _)
+
+theorem Saved.read {s t : State} (h : Saved s t) (d : Nat)
+ (hd : d + 8 ≤ 8 ∨ 16 ≤ d) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [h.regs, h.mem]
+ exact Mem.readW_writeW_sep (Offset.sep _ hd (by omega) (by decide)) (by decide)
+
+theorem Saved.words {s t : State} {p : Params} {pass lane slice old counter : Nat}
+ (h : Saved s t) (words : AddressHeader.Words p pass lane slice old s)
+ (value : s.gpr .rax = BitVec.ofNat 64 counter) :
+ AddressHeader.Words p pass lane slice counter t := by
+ refine ⟨(h.read 0 (by decide) (by decide)).trans words.passWord,
+ ?_, ?_, (h.read 240 (by decide) (by decide)).trans words.blocksWord,
+ (h.read 72 (by decide) (by decide)).trans words.passesWord,
+ (h.read 112 (by decide) (by decide)).trans words.variantWord, ?_⟩
+ · rw [h.regs]; exact words.laneWord
+ · rw [h.regs]; exact words.sliceWord
+ · rw [h.regs, h.mem, Mem.readW_writeW_self64, value]
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelect.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelect.lean
new file mode 100644
index 000000000..2426c91d0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelect.lean
@@ -0,0 +1,114 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheSave
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressGeneration
+
+/-! Regenerate only when the public one-based block counter changes. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCache
+
+def wanted (s : State) : Nat := (s.gpr .r15).toNat / 128 + 1
+
+def writes (s : State) : List Region :=
+ [⟨AddressCalls.work s, 8192⟩, below (s.gpr .rsp) 8, ⟨off (s.gpr .rbp) 8, 8⟩]
+
+structure Ready (p : Params) (pass lane slice old : Nat) (s : State) : Prop where
+ layout : AddressCalls.Ready s
+ reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ write : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ words : AddressHeader.Words p pass lane slice old s
+ cached : counter (s.gpr .r15) = s.mem.readW (off (s.gpr .rbp) 8) 64 →
+ blockAt s.mem (off (AddressCalls.work s) 6144) = addressBlock p pass lane slice (wanted s)
+
+theorem ready_zero (p : Params) (pass lane slice : Nat) (s : State)
+ (layout : AddressCalls.Ready s)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (write : InRegions s.wr (off (s.gpr .rbp) 8) 8)
+ (words : AddressHeader.Words p pass lane slice 0 s) : Ready p pass lane slice 0 s :=
+ ⟨layout, reads, write, words, fun same => False.elim
+ (counter_ne_zero _ (same.trans words.counterWord))⟩
+
+structure Selected (s t : State) (p : Params) (pass lane slice : Nat) : Prop where
+ block : blockAt t.mem (off (AddressCalls.work s) 6144) = addressBlock p pass lane slice (wanted s)
+ layout : AddressCalls.Ready t
+ work_eq : AddressCalls.work t = AddressCalls.work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ counterWord : t.mem.readW (off (t.gpr .rbp) 8) 64 = counter (s.gpr .r15)
+
+theorem check_stable {s a : State} (h : AddressCalls.Ready s) (k : Divide.Keeps [.rax] s a) :
+ AddressCalls.Stable s a := by
+ apply AddressCalls.stable_of_frame h _ k.rd k.wr _ k.mxcsr
+ · intro r hr
+ apply k.regs
+ 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
+ · rw [k.mem]; exact Frame.refl _ _
+
+theorem selected_ok (p : Params) (pass lane slice old : Nat) (s : State)
+ (h : Ready p pass lane slice old s) :
+ WP isa select s (Selected s · p pass lane slice) := by
+ unfold select
+ refine WP.seq ((check_ok s (h.reads 8 (by simp))).mono ?_)
+ rintro a ⟨value, flag, keeps⟩
+ have stableA := check_stable h.layout keeps
+ refine WP.ite (decide (counter (s.gpr .r15) = s.mem.readW (off (s.gpr .rbp) 8) 64))
+ (by simp only [eval, flag]) ?_ ?_
+ · intro same
+ have equal := of_decide_eq_true same
+ apply WP.of_runBlock
+ simp only [runBlock_nil, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, stableA.ready, stableA.work_eq, stableA.regs, keeps.rd, keeps.wr,
+ ?_, keeps.mxcsr, ?_⟩
+ · rw [keeps.mem]; exact h.cached equal
+ · rw [keeps.mem]; exact Frame.refl _ _
+ · rw [stableA.regs .rbp (by simp [calleeSaved]), keeps.mem]; exact equal.symm
+ · intro _
+ have write : InRegions a.wr (off (a.gpr .rbp) 8) 8 := by
+ rw [keeps.wr, stableA.regs .rbp (by simp [calleeSaved])]; exact h.write
+ refine WP.seq ((save_ready a stableA.ready write).mono ?_)
+ intro b saved
+ have words : AddressHeader.Words p pass lane slice (wanted s) b := by
+ apply saved.words (stableA.words h.layout h.words)
+ rw [value, counter_nat]; rfl
+ have reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (b.rd ++ b.wr) (off (b.gpr .rbp) d) 8 := by
+ rw [saved.rd, saved.wr, saved.regs]; exact stableA.reads h.reads
+ refine (AddressCalls.code_ok p pass lane slice (wanted s) b saved.ready reads words).mono ?_
+ rintro t ⟨generated, mx⟩
+ have workB : AddressCalls.work b = AddressCalls.work s := saved.work_eq.trans stableA.work_eq
+ have regsB (r : Reg) (hr : r ∈ calleeSaved) : b.gpr r = s.gpr r :=
+ (congrFun saved.regs r).trans (stableA.regs r hr)
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r :=
+ fun r hr => (generated.regs r hr).trans (regsB r hr)
+ have firstFrame : Frame (writes s) s.mem b.mem := by
+ have frame := saved.frame
+ rw [stableA.regs .rbp (by simp [calleeSaved]), keeps.mem] at frame
+ exact frame.mono (by intro r hr; simp only [List.mem_singleton] at hr; subst r; simp [writes])
+ have finalFrame : Frame (writes s) b.mem t.mem := by
+ have frame := generated.frame
+ rw [AddressCalls.writes, workB, regsB .rsp (by simp [calleeSaved])] at frame
+ exact frame.mono (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl <;> simp [writes])
+ refine ⟨?_, generated.ready, generated.work.trans workB, regs,
+ generated.rd.trans (saved.rd.trans keeps.rd), generated.wr.trans (saved.wr.trans keeps.wr),
+ firstFrame.trans finalFrame, mx.trans (saved.mxcsr.trans keeps.mxcsr), ?_⟩
+ · have block := generated.block
+ rw [workB] at block
+ exact block
+ · have preserved : t.mem.readW (off (b.gpr .rbp) 8) 64 = b.mem.readW (off (b.gpr .rbp) 8) 64 :=
+ generated.frame.readW (r := ⟨b.gpr .rbp, 272⟩)
+ (Offset.contains_base _ (by decide) (by decide)) (by
+ intro r hr
+ simp only [AddressCalls.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact saved.ready.frameWork
+ · exact saved.ready.frameStack) (by decide)
+ rw [generated.regs .rbp (by simp [calleeSaved]), preserved, saved.regs, saved.mem,
+ Mem.readW_writeW_self64, value]
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelectCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelectCT.lean
new file mode 100644
index 000000000..5652b3b09
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheSelectCT.lean
@@ -0,0 +1,118 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheSelect
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheWordCT
+
+/-! Cache regeneration branches only on public counters. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCache
+
+structure CacheRelated (s t : State) : Prop where
+ prepare : AddressCalls.PrepareRelated s t
+ indices : s.gpr .r15 = t.gpr .r15
+ counters : s.mem.readW (off (s.gpr .rbp) 8) 64 = t.mem.readW (off (t.gpr .rbp) 8) 64
+ leftWrite : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ rightWrite : InRegions t.wr (off (t.gpr .rbp) 8) 8
+
+structure CheckedRelated (s t : State) : Prop where
+ related : CacheRelated s t
+ values : s.gpr .rax = t.gpr .rax
+ flags : s.zf = t.zf
+
+theorem check_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rbp, .r15], s.gpr r = t.gpr r)
+ (.block check) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp, .r15])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+theorem check_public_rel : RelCT isa CacheRelated (.block check) CheckedRelated := by
+ have trace := check_rel.mono (P' := CacheRelated) (by
+ intro s t h r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.prepare.related.bases
+ · exact h.indices) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨check_ok s (h.prepare.leftReads 8 (by simp)), check_ok t (h.prepare.rightReads 8 (by simp))⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨va, fa, ka⟩, ⟨vb, fb, kb⟩⟩ := h
+ have sa := check_stable hp.prepare.related.left ka
+ have sb := check_stable hp.prepare.related.right kb
+ have index : a.gpr .r15 = b.gpr .r15 := (sa.regs .r15 (by simp [calleeSaved])).trans
+ (hp.indices.trans (sb.regs .r15 (by simp [calleeSaved])).symm)
+ have counters : a.mem.readW (off (a.gpr .rbp) 8) 64 = b.mem.readW (off (b.gpr .rbp) 8) 64 := by
+ rw [ka.mem, kb.mem, sa.regs .rbp (by simp [calleeSaved]), sb.regs .rbp (by simp [calleeSaved])]
+ exact hp.counters
+ refine ⟨⟨⟨hp.prepare.related.of_stable sa sb, sa.reads hp.prepare.leftReads,
+ sb.reads hp.prepare.rightReads⟩, index, counters, ?_, ?_⟩, ?_, ?_⟩
+ · rw [ka.wr, sa.regs .rbp (by simp [calleeSaved])]; exact hp.leftWrite
+ · rw [kb.wr, sb.regs .rbp (by simp [calleeSaved])]; exact hp.rightWrite
+ · rw [va, vb, hp.indices]
+ · rw [fa, fb, hp.indices, hp.counters]
+
+theorem save_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block save) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem save_public_rel : RelCT isa CheckedRelated (.block save) AddressCalls.PrepareRelated := by
+ have trace := save_rel.mono (P' := CheckedRelated)
+ (fun _ _ h => h.related.prepare.related.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨save_ready s h.related.prepare.related.left h.related.leftWrite,
+ save_ready t h.related.prepare.related.right h.related.rightWrite⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨⟨ha.ready, hb.ready, ?_, ?_, ha.work_eq.trans
+ (hp.related.prepare.related.work.trans hb.work_eq.symm)⟩, ?_, ?_⟩
+ · rw [ha.regs, hb.regs]; exact hp.related.prepare.related.bases
+ · rw [ha.regs, hb.regs]; exact hp.related.prepare.related.stacks
+ · rw [ha.rd, ha.wr, ha.regs]; exact hp.related.prepare.leftReads
+ · rw [hb.rd, hb.wr, hb.regs]; exact hp.related.prepare.rightReads
+
+theorem select_trace : RelCT isa CacheRelated select (fun _ _ => True) := by
+ have noop : RelCT isa (fun _ _ : State => True) (.block []) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+ have branches : RelCT isa CheckedRelated
+ (.ite .e (.block []) (.seq (.block save) Impl.Argon2.X86_64.AddressCalls.code))
+ (fun _ _ => True) :=
+ RelCT.ite (by intro s t h; simp only [eval, h.flags])
+ (noop.mono (fun _ _ _ => trivial) (fun _ _ h => h))
+ ((save_public_rel.seq AddressCalls.code_rel).mono (fun _ _ h => h.1) (fun _ _ _ => trivial))
+ exact check_public_rel.seq branches
+
+structure ReadyRelated (p : Spec.Argon2.Params) (pass lane slice old : Nat) (s t : State) : Prop where
+ left : Ready p pass lane slice old s
+ right : Ready p pass lane slice old t
+ pubs : CacheRelated s t
+
+theorem select_public_rel (p : Spec.Argon2.Params) (pass lane slice old : Nat) :
+ RelCT isa (ReadyRelated p pass lane slice old) select WordRelated := by
+ have trace := select_trace.mono (P' := ReadyRelated p pass lane slice old)
+ (fun _ _ h => h.pubs) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨selected_ok p pass lane slice old s h.left, selected_ok p pass lane slice old t h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨⟨ha.layout, hb.layout, ?_, ?_, ha.work_eq.trans
+ (hp.pubs.prepare.related.work.trans hb.work_eq.symm)⟩, ?_⟩
+ · exact (ha.regs .rbp (by simp [calleeSaved])).trans
+ (hp.pubs.prepare.related.bases.trans (hb.regs .rbp (by simp [calleeSaved])).symm)
+ · exact (ha.regs .rsp (by simp [calleeSaved])).trans
+ (hp.pubs.prepare.related.stacks.trans (hb.regs .rsp (by simp [calleeSaved])).symm)
+ · exact (ha.regs .r15 (by simp [calleeSaved])).trans
+ (hp.pubs.indices.trans (hb.regs .r15 (by simp [calleeSaved])).symm)
+
+theorem code_rel (p : Spec.Argon2.Params) (pass lane slice old : Nat) :
+ RelCT isa (ReadyRelated p pass lane slice old) code (fun _ _ => True) :=
+ (select_public_rel p pass lane slice old).seq word_rel
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheState.lean
new file mode 100644
index 000000000..fbed7f0c3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheState.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCache
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelStable
+
+/-! Retain the filling header and allocation across independent-address regeneration. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Selected.frame_word {s t : State} {p : Params} {pass lane slice : Nat}
+ (layout : AddressCalls.Ready s) (h : Selected s t p pass lane slice)
+ (d : Nat) (bound : d + 8 ≤ 272) (separate : d + 8 ≤ 8 ∨ 16 ≤ d) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [h.regs .rbp (by simp [calleeSaved])]
+ have sub : Region.Sub ⟨off (s.gpr .rbp) d, 8⟩ ⟨s.gpr .rbp, 272⟩ := Offset.sub_base _ bound
+ exact h.frame.readW (r := ⟨off (s.gpr .rbp) d, 8⟩) (Region.contains_self _ _) (by
+ intro r hr
+ simp only [writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact layout.frameWork.sub_left sub
+ · exact layout.frameStack.sub_left sub
+ · exact Offset.disjoint _ separate (by omega) (by decide)) (by decide)
+
+theorem Selected.words {s t : State} {p : Params} {pass lane slice old : Nat}
+ (ready : Ready p pass lane slice old s) (h : Selected s t p pass lane slice) :
+ AddressHeader.Words p pass lane slice (wanted s) t := by
+ exact ⟨(h.frame_word ready.layout 0 (by decide) (by decide)).trans ready.words.passWord,
+ (h.regs .rbx (by simp [calleeSaved])).trans ready.words.laneWord,
+ (h.regs .r14 (by simp [calleeSaved])).trans ready.words.sliceWord,
+ (h.frame_word ready.layout 240 (by decide) (by decide)).trans ready.words.blocksWord,
+ (h.frame_word ready.layout 72 (by decide) (by decide)).trans ready.words.passesWord,
+ (h.frame_word ready.layout 112 (by decide) (by decide)).trans ready.words.variantWord,
+ h.counterWord.trans (counter_nat _)⟩
+
+theorem Ready.of_keeps {p : Params} {pass lane slice old : Nat} {s t : State}
+ (h : Ready p pass lane slice old s) (k : Divide.Keeps ReferenceMap.changed s t) :
+ Ready p pass lane slice old t := by
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r := by
+ intro r hr
+ apply k.regs
+ 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
+ have stable := AddressCalls.stable_of_frame h.layout regs k.rd k.wr
+ (by rw [k.mem]; exact Frame.refl _ _) k.mxcsr
+ refine ⟨stable.ready, stable.reads h.reads, ?_, stable.words h.layout h.words, ?_⟩
+ · rw [k.wr, k.regs .rbp (by decide)]; exact h.write
+ · intro same
+ have wanted' : wanted t = wanted s := by unfold wanted; rw [k.regs .r15 (by decide)]
+ rw [k.mem, k.regs .rbp (by decide), k.regs .r15 (by decide)] at same
+ rw [k.mem, stable.work_eq, wanted']
+ exact h.cached same
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWord.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWord.lean
new file mode 100644
index 000000000..095983bc2
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWord.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheMeta
+
+/-! Read exactly the public indexed word of the cached address block. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCache
+
+def wordAddress (s : State) : Addr :=
+ s.gpr .rcx + s.gpr .rax * BitVec.ofNat 64 8 + BitVec.ofInt 64 6144
+
+theorem wordRead_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (wordAddress s) 8) :
+ WP isa (.block wordRead) s fun t => t.gpr .rdi = s.mem.readW (wordAddress s) 64 ∧
+ Divide.Keeps [.rdi] s t := by
+ dsimp only [wordAddress] at hr
+ apply WP.of_runBlock
+ simp only [wordRead, wordAddress, runBlock_cons, runStep_some, runBlock_nil,
+ exec, readSrc, State.load64, State.ea, hr, Option.map_some,
+ Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+ all_goals rfl
+
+theorem wordAddress_args {s a : State}
+ (scratch : a.gpr .rcx = AddressCalls.work s)
+ (index : a.gpr .rax = s.gpr .r15 &&& 127) :
+ wordAddress a = off (off (AddressCalls.work s) 6144) (8 * ((s.gpr .r15).toNat % 128)) := by
+ unfold wordAddress off
+ rw [scratch, index, index_nat, ← BitVec.ofNat_mul, Nat.mul_comm]
+ change AddressCalls.work s + BitVec.ofNat 64 (8 * ((s.gpr .r15).toNat % 128)) +
+ BitVec.ofNat 64 6144 = _
+ rw [BitVec.add_assoc, BitVec.add_comm (BitVec.ofNat 64 (8 * ((s.gpr .r15).toNat % 128)))
+ (BitVec.ofNat 64 6144), ← BitVec.add_assoc]
+
+theorem word_ok (s : State) (h : AddressCalls.Ready s) :
+ WP isa Impl.Argon2.X86_64.AddressCache.word s fun t => t.gpr .rdi =
+ (blockAt s.mem (off (AddressCalls.work s) 6144))[(s.gpr .r15).toNat % 128]'(Nat.mod_lt _ (by decide)) ∧
+ Divide.Keeps [.rcx, .rax, .rdi] s t := by
+ unfold Impl.Argon2.X86_64.AddressCache.word
+ refine WP.seq ((wordArgs_ok s h.frameRead).mono ?_)
+ rintro a ⟨scratch, index, keeps⟩
+ have address := wordAddress_args scratch index
+ have read : InRegions (a.rd ++ a.wr) (wordAddress a) 8 := by
+ rw [address, keeps.rd, keeps.wr]
+ have cover := AddressCalls.work_cover s h 6144 1024 (by decide)
+ have writable := cover _ _ ⟨⟨off (AddressCalls.work s) 6144, 1024⟩, by simp,
+ Offset.contains_base _ (d := 8 * ((s.gpr .r15).toNat % 128)) (n := 8) (k := 1024)
+ (by have := Nat.mod_lt (s.gpr .r15).toNat (by decide : 0 < 128); omega) (by omega)⟩
+ obtain ⟨r, hr, hc⟩ := writable
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ refine (wordRead_ok a read).mono ?_
+ rintro t ⟨value, tail⟩
+ refine ⟨?_, (keeps.mono (by decide)).trans (tail.mono (by decide))⟩
+ rw [value, address, keeps.mem]
+ change s.mem.readW _ 64 = (blockAt _ _)[(⟨_, Nat.mod_lt _ (by decide)⟩ : Fin 128)]
+ rw [blockAt_get]
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWordCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWordCT.lean
new file mode 100644
index 000000000..dd5e7ad90
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCacheWordCT.lean
@@ -0,0 +1,45 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheWord
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressGenerationCT
+
+/-! The cached random word is secret; its read address is public. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCache
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCache
+
+structure WordRelated (s t : State) : Prop where
+ layout : AddressCalls.Related s t
+ indices : s.gpr .r15 = t.gpr .r15
+
+theorem wordArgs_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rbp, .r15], s.gpr r = t.gpr r)
+ (.block wordArgs) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp, .r15])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+theorem wordRead_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rcx, .rax], s.gpr r = t.gpr r)
+ (.block wordRead) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rcx, .rax])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+theorem word_rel : RelCT isa WordRelated Impl.Argon2.X86_64.AddressCache.word (fun _ _ => True) := by
+ have trace := wordArgs_rel.mono (P' := WordRelated) (by
+ intro s t h r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.layout.bases
+ · exact h.indices) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨wordArgs_ok s h.layout.left.frameRead, wordArgs_ok t h.layout.right.frameRead⟩)
+ have args : RelCT isa WordRelated (.block wordArgs)
+ (fun s t => ∀ r ∈ [Reg.rcx, .rax], s.gpr r = t.gpr r) := full.mono (fun _ _ h => h) (by
+ intro a b h r hr
+ obtain ⟨_, s, t, hp, ⟨sa, ia, _⟩, ⟨sb, ib, _⟩⟩ := h
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact sa.trans (hp.layout.work.trans sb.symm)
+ · exact ia.trans ((congrArg (· &&& 127) hp.indices).trans ib.symm))
+ exact args.seq wordRead_rel
+
+end VG.Proof.Argon2.X86_64.AddressCache
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCalls.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCalls.lean
new file mode 100644
index 000000000..9018d7867
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCalls.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsStage
+import VerifiedGarbage.Proof.Argon2.AddressInput
+
+/-! Both compression calls produce exactly the reviewed independent-address block. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCalls
+
+def writes (s : State) : List Region := [⟨work s, 8192⟩, below (s.gpr .rsp) 8]
+
+structure Generated (s t : State) (p : Params) (pass lane slice counter : Nat) : Prop where
+ block : blockAt t.mem (off (work s) 6144) = addressBlock p pass lane slice counter
+ ready : Ready t
+ work : work t = work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s) s.mem t.mem
+
+theorem stage_frame_full {s t : State} {out : Nat} (bound : out + 1024 ≤ 8192)
+ (hf : Frame (stageWrites s out) s.mem t.mem) : Frame (writes s) s.mem t.mem := by
+ apply hf.sub
+ intro r hr
+ simp only [stageWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact ⟨⟨work s, 8192⟩, by simp [writes], Offset.sub_base _ bound⟩
+ · exact ⟨⟨work s, 8192⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+
+theorem zero_preserved {s t : State} (h : Ready s)
+ (hf : Frame (stageWrites s 4096) s.mem t.mem) :
+ blockAt t.mem (off (work s) 7168) = blockAt s.mem (off (work s) 7168) := by
+ apply FillCompress.block_frame hf
+ intro r hr
+ simp only [stageWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact Offset.disjoint _ (by decide) (by decide) (by decide)
+ · exact Offset.disjoint_base _ (by decide) (by decide)
+ · exact (h.stackWork.sub_right (Offset.sub_base _ (by decide))).symm
+
+theorem calls_ok (p : Params) (pass lane slice counter : Nat) (s : State) (h : Ready s)
+ (zero : blockAt s.mem (off (work s) 7168) = zeroBlock)
+ (input : blockAt s.mem (off (work s) 5120) = Proof.Argon2.addressInput p pass lane slice counter) :
+ WP isa calls s (Generated s · p pass lane slice counter) := by
+ unfold calls
+ refine WP.seq ((stage_ok s h 7168 5120 4096 (by decide) (by decide) (by decide)
+ (by decide) (by decide) (by decide)).mono ?_)
+ intro a first
+ refine (stage_ok a first.ready 7168 4096 6144 (by decide) (by decide) (by decide)
+ (by decide) (by decide) (by decide)).mono ?_
+ intro t second
+ refine ⟨?_, second.ready, second.work.trans first.work,
+ fun r hr => (second.regs r hr).trans (first.regs r hr),
+ second.rd.trans first.rd, second.wr.trans first.wr, ?_⟩
+ · have result := second.result
+ rw [first.work, zero_preserved h first.frame, zero, first.result, zero, input] at result
+ rw [Proof.Argon2.addressBlock_eq]
+ exact result
+ · have next := stage_frame_full (by decide) second.frame
+ simp only [writes, first.work, first.regs .rsp (by simp [calleeSaved])] at next
+ exact (stage_frame_full (by decide) first.frame).trans next
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsArgs.lean
new file mode 100644
index 000000000..1794c8b57
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsArgs.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressCalls
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeaderWords
+
+/-! Independent-address compression arguments from one fixed frame read. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCalls
+
+def work (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 248) 64
+
+def displacement (n : Nat) : Addr := BitVec.signExtend 64 (BitVec.ofNat 32 n)
+
+theorem pointer_ok (s : State) (offset : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8) :
+ WP isa (.block (pointer offset)) s fun t =>
+ t.gpr .rdi = work s + displacement offset ∧ Divide.Keeps [.rdi] s t := by
+ apply WP.of_runBlock
+ simp only [pointer, work, displacement, runBlock_cons, runStep_some, runBlock_nil,
+ exec, readSrc, State.load64, ea_at, hr, execAlu, RegUpd.gpr_setReg,
+ RegUpd.gpr_arithFlags, ite_true, Option.map_some,
+ Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem args_ok (s : State) (x y out : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8) :
+ WP isa (.block (args x y out)) s fun t =>
+ t.gpr .rcx = work s ∧ t.gpr .rdi = work s + displacement x ∧
+ t.gpr .rsi = work s + displacement y ∧ t.gpr .rdx = work s + displacement out ∧
+ Divide.Keeps [.rcx, .rdi, .rsi, .rdx] s t := by
+ apply WP.of_runBlock
+ simp only [args, work, displacement, runBlock_cons, runStep_some, runBlock_nil,
+ exec, readSrc, State.load64, ea_at, hr, execAlu, RegUpd.gpr_setReg,
+ RegUpd.gpr_arithFlags, reduceCtorEq, ite_true, ite_false, Option.map_some,
+ Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2.1,
+ hr.2.2.1, hr.2.2.2, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsCT.lean
new file mode 100644
index 000000000..35a0234b3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsCT.lean
@@ -0,0 +1,94 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCalls
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCallCT
+
+/-! The two address-generation compression calls have public fixed addresses. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCalls
+
+structure Related (s t : State) : Prop where
+ left : Ready s
+ right : Ready t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ work : work s = work t
+
+structure CallRelated (s t : State) : Prop where
+ left : FillCompress.CallReady s
+ right : FillCompress.CallReady t
+ args : ∀ r ∈ [Reg.rdi, .rsi, .rdx, .rcx, .rsp], s.gpr r = t.gpr r
+
+theorem first_args_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (args 7168 5120 4096)) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem second_args_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (args 7168 4096 6144)) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem args_public_rel (x y out : Nat)
+ (hx : 4096 ≤ x) (hy : 4096 ≤ y) (ho : 4096 ≤ out)
+ (bx : x + 1024 ≤ 8192) (by_ : y + 1024 ≤ 8192) (bo : out + 1024 ≤ 8192)
+ (argTrace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (args x y out)) (fun _ _ => True)) :
+ RelCT isa Related (.block (args x y out)) CallRelated := by
+ have trace := argTrace.mono (P' := Related) (fun _ _ hp => hp.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t hp =>
+ ⟨args_nat_ok s hp.left x y out (by omega) (by omega) (by omega),
+ args_nat_ok t hp.right x y out (by omega) (by omega) (by omega)⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨args_call_ready s a hp.left x y out hx hy ho bx by_ bo ha,
+ args_call_ready t b hp.right x y out hx hy ho bx by_ bo hb, ?_⟩
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact ha.left.trans ((congrArg (fun p => off p x) hp.work).trans hb.left.symm)
+ · exact ha.right.trans ((congrArg (fun p => off p y) hp.work).trans hb.right.symm)
+ · exact ha.output.trans ((congrArg (fun p => off p out) hp.work).trans hb.output.symm)
+ · exact ha.scratch.trans (hp.work.trans hb.scratch.symm)
+ · exact (ha.keeps.regs .rsp (by decide)).trans
+ (hp.stacks.trans (hb.keeps.regs .rsp (by decide)).symm)
+
+theorem stage_rel (x y out : Nat)
+ (hx : 4096 ≤ x) (hy : 4096 ≤ y) (ho : 4096 ≤ out)
+ (bx : x + 1024 ≤ 8192) (by_ : y + 1024 ≤ 8192) (bo : out + 1024 ≤ 8192)
+ (argTrace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (args x y out)) (fun _ _ => True)) :
+ RelCT isa Related (stage x y out) Related := by
+ have call := FillCompress.call_rel Spec.Argon2.compressApi.name (P := CallRelated)
+ (fun _ _ hp => ⟨hp.left, hp.right, hp.args .rdi (by simp), hp.args .rsi (by simp),
+ hp.args .rdx (by simp), hp.args .rcx (by simp), hp.args .rsp (by simp)⟩)
+ have trace := (args_public_rel x y out hx hy ho bx by_ bo argTrace).seq call
+ have full := trace.wpDep (fun s t hp =>
+ ⟨stage_ok s hp.left x y out hx hy ho bx by_ bo,
+ stage_ok t hp.right x y out hx hy ho bx by_ bo⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact ⟨ha.ready, hb.ready,
+ (ha.regs .rbp (by simp [calleeSaved])).trans
+ (hp.bases.trans (hb.regs .rbp (by simp [calleeSaved])).symm),
+ (ha.regs .rsp (by simp [calleeSaved])).trans
+ (hp.stacks.trans (hb.regs .rsp (by simp [calleeSaved])).symm),
+ ha.work.trans (hp.work.trans hb.work.symm)⟩
+
+theorem calls_rel : RelCT isa Related calls Related :=
+ (stage_rel 7168 5120 4096 (by decide) (by decide) (by decide)
+ (by decide) (by decide) (by decide) first_args_rel).seq
+ (stage_rel 7168 4096 6144 (by decide) (by decide) (by decide)
+ (by decide) (by decide) (by decide) second_args_rel)
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsClear.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsClear.lean
new file mode 100644
index 000000000..33149882b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsClear.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsStage
+import VerifiedGarbage.Proof.Argon2.X86_64.ClearBlock
+
+/-! Clear an address-generation block, retaining the allocation invariants. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCalls
+
+structure Cleared (s t : State) (offset : Nat) : Prop where
+ block : blockAt t.mem (off (work s) offset) = zeroBlock
+ ready : Ready t
+ work_eq : work t = work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨off (work s) offset, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem clearAt_ok (s : State) (h : Ready s) (offset : Nat) (bound : offset + 1024 ≤ 8192) :
+ WP isa (clearAt offset) s (Cleared s · offset) := by
+ unfold clearAt
+ refine WP.seq ((pointer_ok s offset h.frameRead).mono ?_)
+ rintro a ⟨dest, keeps⟩
+ rw [displacement_eq offset (by omega)] at dest
+ have write : Covers [⟨a.gpr .rdi, 1024⟩] a.wr := by
+ rw [dest, keeps.wr]; exact work_cover s h offset 1024 bound
+ refine (ClearBlock.code_ok a write).mono ?_
+ rintro t ⟨zero, frame, tk, mx⟩
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r := by
+ intro r hr
+ have ne : r ≠ .rax ∧ r ≠ .rdi := by
+ 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 (tk.1 r ne.1).trans (keeps.regs r (by simpa only [List.mem_cons, List.not_mem_nil, or_false] using ne.2))
+ have rd := tk.2.1.trans keeps.rd
+ have wr := tk.2.2.trans keeps.wr
+ have hf : Frame [⟨off (work s) offset, 1024⟩] s.mem t.mem := by
+ rw [dest, keeps.mem] at frame; exact frame
+ have bigger : Frame (stageWrites s offset) s.mem t.mem :=
+ hf.mono (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ simp [stageWrites])
+ obtain ⟨ready, work'⟩ := ready_of_frame h offset bound regs rd wr bigger
+ rw [dest] at zero
+ exact ⟨zero, ready, work', regs, rd, wr, hf, mx.trans keeps.mxcsr⟩
+
+theorem Cleared.full_frame {s t : State} {offset : Nat} (h : Cleared s t offset)
+ (bound : offset + 1024 ≤ 8192) : Frame [⟨work s, 8192⟩] s.mem t.mem := by
+ apply h.frame.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨work s, 8192⟩, by simp, Offset.sub_base _ bound⟩
+
+theorem frame_word {s t : State} (h : Ready s) (frame : Frame [⟨work s, 8192⟩] s.mem t.mem)
+ (d : Nat) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (s.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 :=
+ frame.readW (r := ⟨s.gpr .rbp, 272⟩) (Offset.contains_base _ bound (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r; exact h.frameWork) (by decide)
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsLayout.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsLayout.lean
new file mode 100644
index 000000000..81cc0442b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsLayout.lean
@@ -0,0 +1,79 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCall
+
+/-! Permissions and separation for either address-generation compression call. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64
+
+structure Ready (s : State) : Prop where
+ frameRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8
+ workWrite : Covers [⟨work s, 8192⟩] s.wr
+ frameWork : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨work s, 8192⟩
+ frameStack : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint (below (s.gpr .rsp) 8)
+ stackWork : (below (s.gpr .rsp) 8).Disjoint ⟨work s, 8192⟩
+
+theorem displacement_eq (n : Nat) (bound : n ≤ 8192) : displacement n = BitVec.ofNat 64 n := by
+ have n32 : n < 2 ^ 32 := by omega
+ have msb : (BitVec.ofNat 32 n).msb = false := by
+ rw [BitVec.msb_eq_false_iff_two_mul_lt, BitVec.toNat_ofNat, Nat.mod_eq_of_lt n32]
+ omega
+ unfold displacement
+ rw [BitVec.signExtend_eq_setWidth_of_msb_false msb,
+ BitVec.setWidth_ofNat_of_le_of_lt (by decide) n32]
+
+theorem work_cover (s : State) (h : Ready s) (d n : Nat) (hd : d + n ≤ 8192) :
+ Covers [⟨off (work s) d, n⟩] s.wr := by
+ have sub : Covers [⟨off (work s) d, n⟩] [⟨work s, 8192⟩] := by
+ apply Covers.of_sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨work s, 8192⟩, by simp, d, rfl, hd⟩
+ exact fun p n hp => h.workWrite p n (sub p n hp)
+
+structure Args (s a : State) (x y out : Nat) : Prop where
+ scratch : a.gpr .rcx = work s
+ left : a.gpr .rdi = off (work s) x
+ right : a.gpr .rsi = off (work s) y
+ output : a.gpr .rdx = off (work s) out
+ keeps : Divide.Keeps [.rcx, .rdi, .rsi, .rdx] s a
+
+theorem args_nat_ok (s : State) (h : Ready s) (x y out : Nat)
+ (hx : x ≤ 8192) (hy : y ≤ 8192) (ho : out ≤ 8192) :
+ WP isa (.block (Impl.Argon2.X86_64.AddressCalls.args x y out)) s (Args s · x y out) := by
+ refine (args_ok s x y out h.frameRead).mono ?_
+ rintro a ⟨scratch, left, right, output, keeps⟩
+ rw [displacement_eq x hx] at left
+ rw [displacement_eq y hy] at right
+ rw [displacement_eq out ho] at output
+ exact ⟨scratch, left, right, output, keeps⟩
+
+theorem args_call_ready (s a : State) (h : Ready s) (x y out : Nat)
+ (hx : 4096 ≤ x) (hy : 4096 ≤ y) (ho : 4096 ≤ out)
+ (bx : x + 1024 ≤ 8192) (by_ : y + 1024 ≤ 8192) (bo : out + 1024 ≤ 8192)
+ (args : Args s a x y out) : FillCompress.CallReady a := by
+ have read (d : Nat) (hd : d + 1024 ≤ 8192) :
+ Covers [⟨off (work s) d, 1024⟩] (a.rd ++ a.wr) := by
+ rw [args.keeps.rd, args.keeps.wr]
+ intro p n hp
+ obtain ⟨r, hr, hc⟩ := work_cover s h d 1024 hd p n hp
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ have sp : a.gpr .rsp = s.gpr .rsp := args.keeps.regs .rsp (by decide)
+ refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [args.left]; exact read x bx
+ · rw [args.right]; exact read y by_
+ · rw [args.output, args.keeps.wr]; exact work_cover s h out 1024 bo
+ · rw [args.scratch, args.keeps.wr]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero]
+ using work_cover s h 0 4096 (by decide)
+ · rw [args.left, args.scratch]; exact Offset.disjoint_base _ hx (by omega)
+ · rw [args.right, args.scratch]; exact Offset.disjoint_base _ hy (by omega)
+ · rw [args.output, args.scratch]; exact Offset.disjoint_base _ ho (by omega)
+ · rw [sp, args.left]; exact h.stackWork.sub_right (Offset.sub_base _ bx)
+ · rw [sp, args.right]; exact h.stackWork.sub_right (Offset.sub_base _ by_)
+ · rw [sp, args.output]; exact h.stackWork.sub_right (Offset.sub_base _ bo)
+ · rw [sp, args.scratch]; exact h.stackWork.sub_right (Region.sub_prefix (by decide))
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsMx.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsMx.lean
new file mode 100644
index 000000000..faed3e606
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsMx.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCalls
+
+/-! The baseline independent-address calls preserve all MXCSR bits. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCalls
+
+theorem compression_noMx : VG.Impl.Argon2.X86_64.compress.allInstrs (fun i => !loadsMxcsr i) = true :=
+ by lit_decide
+
+theorem stage_noMx (x y out : Nat) : (stage x y out).allInstrs (fun i => !loadsMxcsr i) = true := by
+ change ((Code.block (args x y out) : Prog isa).allInstrs (fun i => !loadsMxcsr i) &&
+ VG.Impl.Argon2.X86_64.compress.allInstrs (fun i => !loadsMxcsr i)) = true
+ rw [compression_noMx]
+ rfl
+
+theorem calls_noMx : calls.allInstrs (fun i => !loadsMxcsr i) = true := by
+ change ((stage 7168 5120 4096).allInstrs (fun i => !loadsMxcsr i) &&
+ (stage 7168 4096 6144).allInstrs (fun i => !loadsMxcsr i)) = true
+ rw [stage_noMx, stage_noMx]
+ rfl
+
+theorem calls_mx_ok (p : Spec.Argon2.Params) (pass lane slice counter : Nat) (s : State) (h : Ready s)
+ (zero : Spec.Argon2.blockAt s.mem (off (work s) 7168) = Spec.Argon2.zeroBlock)
+ (input : Spec.Argon2.blockAt s.mem (off (work s) 5120) =
+ Proof.Argon2.addressInput p pass lane slice counter) :
+ WP isa calls s fun t => Generated s t p pass lane slice counter ∧ t.mxcsr = s.mxcsr :=
+ WP.mono_mx calls_noMx (calls_ok p pass lane slice counter s h zero input)
+ (fun _ generated mx => ⟨generated, mx⟩)
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsPrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsPrepare.lean
new file mode 100644
index 000000000..ef17576d0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsPrepare.lean
@@ -0,0 +1,159 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsClear
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeaderCorrect
+
+/-! Prepare the independent-address input and zero block from arbitrary scratch. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCalls
+
+structure Stable (s t : State) : Prop where
+ ready : Ready t
+ work_eq : work t = work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨work s, 8192⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem Stable.trans {s a t : State} (h : Stable s a) (k : Stable a t) : Stable s t := by
+ have hf := k.frame
+ rw [h.work_eq] at hf
+ exact ⟨k.ready, k.work_eq.trans h.work_eq, fun r hr => (k.regs r hr).trans (h.regs r hr),
+ k.rd.trans h.rd, k.wr.trans h.wr, h.frame.trans hf, k.mxcsr.trans h.mxcsr⟩
+
+theorem Cleared.stable {s t : State} {offset : Nat} (h : Cleared s t offset)
+ (bound : offset + 1024 ≤ 8192) : Stable s t :=
+ ⟨h.ready, h.work_eq, h.regs, h.rd, h.wr, h.full_frame bound, h.mxcsr⟩
+
+theorem stable_of_frame {s t : State} (h : Ready s)
+ (regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r) (rd : t.rd = s.rd) (wr : t.wr = s.wr)
+ (frame : Frame [⟨work s, 8192⟩] s.mem t.mem) (mx : t.mxcsr = s.mxcsr) : Stable s t := by
+ have bp := regs .rbp (by simp [calleeSaved])
+ have sp := regs .rsp (by simp [calleeSaved])
+ have work' : work t = work s := by
+ unfold work
+ rw [bp, frame_word h frame 248 (by decide)]
+ refine ⟨⟨?_, ?_, ?_, ?_, ?_⟩, work', regs, rd, wr, frame, mx⟩
+ · rw [rd, wr, bp]; exact h.frameRead
+ · rw [work', wr]; exact h.workWrite
+ · rw [bp, work']; exact h.frameWork
+ · rw [bp, sp]; exact h.frameStack
+ · rw [sp, work']; exact h.stackWork
+
+theorem Stable.reads {s t : State} (h : Stable s t)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8) :
+ ∀ d ∈ [0, 8, 72, 112, 240], InRegions (t.rd ++ t.wr) (off (t.gpr .rbp) d) 8 := by
+ rw [h.rd, h.wr, h.regs .rbp (by simp [calleeSaved])]
+ exact reads
+
+theorem Stable.words {s t : State} {p : Params} {pass lane slice counter : Nat} (ready : Ready s) (h : Stable s t)
+ (words : AddressHeader.Words p pass lane slice counter s) :
+ AddressHeader.Words p pass lane slice counter t := by
+ have bp := h.regs .rbp (by simp [calleeSaved])
+ have read (d : Nat) (hd : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [bp]; exact frame_word ready h.frame d hd
+ exact ⟨(read 0 (by decide)).trans words.passWord,
+ (h.regs .rbx (by simp [calleeSaved])).trans words.laneWord,
+ (h.regs .r14 (by simp [calleeSaved])).trans words.sliceWord,
+ (read 240 (by decide)).trans words.blocksWord,
+ (read 72 (by decide)).trans words.passesWord,
+ (read 112 (by decide)).trans words.variantWord,
+ (read 8 (by decide)).trans words.counterWord⟩
+
+theorem pointer_stable {s a : State} (h : Ready s)
+ (k : Divide.Keeps [.rdi] s a) : Stable s a := by
+ apply stable_of_frame h _ k.rd k.wr _ k.mxcsr
+ · intro r hr
+ apply k.regs
+ 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
+ · rw [k.mem]; exact Frame.refl _ _
+
+theorem Stable.input {s t : State} (ready : Ready s) (h : Stable s t) :
+ AddressHeader.input t = AddressHeader.input s := by
+ have value (i : Nat) (hi : i < 7) : AddressHeader.value t i = AddressHeader.value s i := by
+ unfold AddressHeader.value
+ rw [h.regs .rbx (by simp [calleeSaved]), h.regs .r14 (by simp [calleeSaved]),
+ h.regs .rbp (by simp [calleeSaved]),
+ frame_word ready h.frame (Impl.Argon2.X86_64.AddressHeader.frameOffset i)
+ (AddressHeader.offset_bound i hi)]
+ unfold AddressHeader.input
+ rw [value 0 (by decide), value 1 (by decide), value 2 (by decide), value 3 (by decide),
+ value 4 (by decide), value 5 (by decide), value 6 (by decide)]
+
+structure PreparedInput (s t : State) : Prop where
+ stable : Stable s t
+ zero : blockAt t.mem (off (work s) 7168) = zeroBlock
+ input : blockAt t.mem (off (work s) 5120) = AddressHeader.input s
+
+structure Prepared (s t : State) (p : Params) (pass lane slice counter : Nat) : Prop where
+ stable : Stable s t
+ zero : blockAt t.mem (off (work s) 7168) = zeroBlock
+ input : blockAt t.mem (off (work s) 5120) = Proof.Argon2.addressInput p pass lane slice counter
+
+theorem prepare_layout_ok (s : State) (h : Ready s)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ : WP isa prepare s (PreparedInput s) := by
+ unfold prepare
+ refine WP.seq ((clearAt_ok s h 5120 (by decide)).mono ?_)
+ intro a inputClear
+ refine WP.seq ((clearAt_ok a inputClear.ready 7168 (by decide)).mono ?_)
+ intro b zeroClear
+ have stableB := (inputClear.stable (by decide)).trans (zeroClear.stable (by decide))
+ have inputZero : blockAt b.mem (off (work s) 5120) = zeroBlock := by
+ have kept := FillCompress.block_frame zeroClear.frame (p := off (work s) 5120) (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ rw [inputClear.work_eq]
+ exact Offset.disjoint _ (by decide) (by decide) (by decide))
+ exact kept.trans inputClear.block
+ refine WP.seq ((pointer_ok b 5120 zeroClear.ready.frameRead).mono ?_)
+ rintro c ⟨dest, keeps⟩
+ rw [displacement_eq 5120 (by decide)] at dest
+ have stableC := stableB.trans (pointer_stable zeroClear.ready keeps)
+ have dest' : c.gpr .rdi = off (work s) 5120 := by rw [dest, stableB.work_eq]
+ have write : Covers [⟨c.gpr .rdi, 1024⟩] c.wr := by
+ rw [dest, keeps.wr]; exact work_cover b zeroClear.ready 5120 1024 (by decide)
+ have sep : (⟨c.gpr .rbp, 272⟩ : Region).Disjoint ⟨c.gpr .rdi, 1024⟩ := by
+ rw [dest', stableC.regs .rbp (by simp [calleeSaved])]
+ exact h.frameWork.sub_right (Offset.sub_base _ (by decide))
+ have zero : blockAt c.mem (c.gpr .rdi) = zeroBlock := by rw [dest', keeps.mem]; exact inputZero
+ refine (AddressHeader.code_ok c (stableC.reads reads) write sep zero).mono ?_
+ rintro t ⟨input, frame, tk, mx⟩
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = c.gpr r := by
+ intro r hr
+ apply tk.1
+ 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
+ have frame' : Frame [⟨work c, 8192⟩] c.mem t.mem := by
+ apply frame.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ refine ⟨⟨work c, 8192⟩, by simp, ?_⟩
+ rw [dest', stableC.work_eq]
+ exact Offset.sub_base _ (by decide)
+ have stableT := stableC.trans (stable_of_frame stableC.ready regs tk.2.1 tk.2.2 frame' mx)
+ refine ⟨stableT, ?_, ?_⟩
+ · have kept := FillCompress.block_frame frame (p := off (work s) 7168) (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ rw [dest']
+ exact Offset.disjoint _ (by decide) (by decide) (by decide))
+ rw [kept, keeps.mem, ← inputClear.work_eq]
+ exact zeroClear.block
+ · rw [dest'] at input
+ exact input.trans (stableC.input h)
+
+theorem prepare_ok (p : Params) (pass lane slice counter : Nat) (s : State) (h : Ready s)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (words : AddressHeader.Words p pass lane slice counter s) :
+ WP isa prepare s (Prepared s · p pass lane slice counter) :=
+ (prepare_layout_ok s h reads).mono (fun _ k =>
+ ⟨k.stable, k.zero, k.input.trans (AddressHeader.input_spec p pass lane slice counter s words)⟩)
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsStage.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsStage.lean
new file mode 100644
index 000000000..a675ced48
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressCallsStage.lean
@@ -0,0 +1,76 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsLayout
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressSetup
+
+/-! One verified G call within the independent-address scratch layout. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCalls
+
+def stageWrites (s : State) (out : Nat) : List Region :=
+ [⟨off (work s) out, 1024⟩, ⟨work s, 4096⟩, below (s.gpr .rsp) 8]
+
+structure StageDone (s t : State) (x y out : Nat) : Prop where
+ result : blockAt t.mem (off (work s) out) = Spec.Argon2.compress
+ (blockAt s.mem (off (work s) x)) (blockAt s.mem (off (work s) y))
+ ready : Ready t
+ work : work t = work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (stageWrites s out) s.mem t.mem
+
+theorem Args.callee {s a : State} {x y out : Nat} (h : Args s a x y out)
+ (r : Reg) (hr : r ∈ calleeSaved) : a.gpr r = s.gpr r := by
+ apply h.keeps.regs
+ 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
+
+theorem ready_of_frame {s t : State} (h : Ready s) (out : Nat) (ho : out + 1024 ≤ 8192)
+ (regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r) (rd : t.rd = s.rd) (wr : t.wr = s.wr)
+ (frame : Frame (stageWrites s out) s.mem t.mem) : Ready t ∧ work t = work s := by
+ have bp := regs .rbp (by simp [calleeSaved])
+ have sp := regs .rsp (by simp [calleeSaved])
+ have safe : ∀ r ∈ stageWrites s out, (⟨s.gpr .rbp, 272⟩ : Region).Disjoint r := by
+ intro r hr
+ simp only [stageWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact h.frameWork.sub_right (Offset.sub_base _ ho)
+ · exact h.frameWork.sub_right (Region.sub_prefix (by decide))
+ · exact h.frameStack
+ have read : t.mem.readW (off (s.gpr .rbp) 248) 64 = s.mem.readW (off (s.gpr .rbp) 248) 64 :=
+ frame.readW (r := ⟨s.gpr .rbp, 272⟩)
+ (Offset.contains_base _ (by decide) (by decide)) safe (by decide)
+ have work' : work t = work s := by unfold work; rw [bp, read]
+ refine ⟨⟨?_, ?_, ?_, ?_, ?_⟩, work'⟩
+ · rw [rd, wr, bp]; exact h.frameRead
+ · rw [work', wr]; exact h.workWrite
+ · rw [bp, work']; exact h.frameWork
+ · rw [bp, sp]; exact h.frameStack
+ · rw [sp, work']; exact h.stackWork
+
+theorem stage_ok (s : State) (h : Ready s) (x y out : Nat)
+ (hx : 4096 ≤ x) (hy : 4096 ≤ y) (ho : 4096 ≤ out)
+ (bx : x + 1024 ≤ 8192) (by_ : y + 1024 ≤ 8192) (bo : out + 1024 ≤ 8192) :
+ WP isa (stage x y out) s (StageDone s · x y out) := by
+ unfold stage
+ refine WP.seq ((args_nat_ok s h x y out (by omega) (by omega) (by omega)).mono ?_)
+ intro a args
+ have callReady := args_call_ready s a h x y out hx hy ho bx by_ bo args
+ refine (FillCompress.call_ok _ a callReady).mono ?_
+ intro t called
+ have regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r :=
+ fun r hr => (called.regs r hr).trans (args.callee r hr)
+ have rd := called.rd.trans args.keeps.rd
+ have wr := called.wr.trans args.keeps.wr
+ have frame : Frame (stageWrites s out) s.mem t.mem := by
+ have hf := called.frame
+ rw [args.output, args.scratch, args.callee .rsp (by simp [calleeSaved]), args.keeps.mem] at hf
+ exact hf
+ obtain ⟨ready, work'⟩ := ready_of_frame h out bo regs rd wr frame
+ refine ⟨?_, ready, work', regs, rd, wr, frame⟩
+ have result := called.result
+ rw [args.output, args.left, args.right, args.keeps.mem] at result
+ exact result
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGeneration.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGeneration.lean
new file mode 100644
index 000000000..ad97eb8d3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGeneration.lean
@@ -0,0 +1,39 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsPrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsMx
+
+/-! Complete independent-address generation against the reviewed algorithm. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressCalls
+
+theorem code_ok (p : Params) (pass lane slice counter : Nat) (s : State) (h : Ready s)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (words : AddressHeader.Words p pass lane slice counter s) :
+ WP isa code s fun t => Generated s t p pass lane slice counter ∧ t.mxcsr = s.mxcsr := by
+ unfold code
+ refine WP.seq ((prepare_ok p pass lane slice counter s h reads words).mono ?_)
+ intro a prepared
+ have zero : blockAt a.mem (off (work a) 7168) = zeroBlock := by
+ rw [prepared.stable.work_eq]; exact prepared.zero
+ have input : blockAt a.mem (off (work a) 5120) = Proof.Argon2.addressInput p pass lane slice counter := by
+ rw [prepared.stable.work_eq]; exact prepared.input
+ refine (calls_mx_ok p pass lane slice counter a prepared.stable.ready zero input).mono ?_
+ rintro t ⟨generated, mx⟩
+ have frame := generated.frame
+ rw [writes, prepared.stable.work_eq, prepared.stable.regs .rsp (by simp [calleeSaved])] at frame
+ have firstFrame : Frame (writes s) s.mem a.mem :=
+ prepared.stable.frame.mono (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ simp [writes])
+ refine ⟨⟨?_, generated.ready, generated.work.trans prepared.stable.work_eq,
+ fun r hr => (generated.regs r hr).trans (prepared.stable.regs r hr),
+ generated.rd.trans prepared.stable.rd, generated.wr.trans prepared.stable.wr,
+ firstFrame.trans frame⟩, mx.trans prepared.stable.mxcsr⟩
+ have block := generated.block
+ rw [prepared.stable.work_eq] at block
+ exact block
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGenerationCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGenerationCT.lean
new file mode 100644
index 000000000..fe611aec5
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressGenerationCT.lean
@@ -0,0 +1,95 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsCT
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCallsPrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressInputCT
+
+/-! Independent-address generation keeps its entire trace independent of secrets. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressCalls
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressCalls
+
+theorem Related.of_stable {s t a b : State} (h : Related s t)
+ (ha : Stable s a) (hb : Stable t b) : Related a b :=
+ ⟨ha.ready, hb.ready,
+ (ha.regs .rbp (by simp [calleeSaved])).trans
+ (h.bases.trans (hb.regs .rbp (by simp [calleeSaved])).symm),
+ (ha.regs .rsp (by simp [calleeSaved])).trans
+ (h.stacks.trans (hb.regs .rsp (by simp [calleeSaved])).symm),
+ ha.work_eq.trans (h.work.trans hb.work_eq.symm)⟩
+
+theorem input_pointer_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (pointer 5120)) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem zero_pointer_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (pointer 7168)) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+structure PointRelated (s t : State) : Prop where
+ related : Related s t
+ pointer : s.gpr .rdi = t.gpr .rdi
+
+theorem pointer_public_rel (offset : Nat)
+ (trace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (pointer offset)) (fun _ _ => True)) :
+ RelCT isa Related (.block (pointer offset)) PointRelated := by
+ have publicTrace := trace.mono (P' := Related) (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := publicTrace.wpDep (fun s t h =>
+ ⟨pointer_ok s offset h.left.frameRead, pointer_ok t offset h.right.frameRead⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨pa, ka⟩, ⟨pb, kb⟩⟩ := h
+ exact ⟨hp.of_stable (pointer_stable hp.left ka) (pointer_stable hp.right kb),
+ pa.trans ((congrArg (· + displacement offset) hp.work).trans pb.symm)⟩
+
+theorem clearAt_rel (offset : Nat) (bound : offset + 1024 ≤ 8192)
+ (trace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block (pointer offset)) (fun _ _ => True)) :
+ RelCT isa Related (clearAt offset) Related := by
+ have clear := ClearBlock.code_rel.mono (P' := PointRelated)
+ (fun _ _ h => h.pointer) (fun _ _ h => h)
+ have blocks := (pointer_public_rel offset trace).seq clear
+ have full := blocks.wpDep (fun s t h =>
+ ⟨clearAt_ok s h.left offset bound, clearAt_ok t h.right offset bound⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact hp.of_stable (ha.stable bound) (hb.stable bound)
+
+structure PrepareRelated (s t : State) : Prop where
+ related : Related s t
+ leftReads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ rightReads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (t.rd ++ t.wr) (off (t.gpr .rbp) d) 8
+
+theorem prepare_rel : RelCT isa PrepareRelated prepare Related := by
+ have header := AddressHeader.code_rel.mono (P' := PointRelated) (by
+ intro s t h r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.pointer
+ · exact h.related.bases) (fun _ _ h => h)
+ have trace := (clearAt_rel 5120 (by decide) input_pointer_rel).seq
+ ((clearAt_rel 7168 (by decide) zero_pointer_rel).seq
+ ((pointer_public_rel 5120 input_pointer_rel).seq header))
+ have narrowed := trace.mono (P' := PrepareRelated) (fun _ _ h => h.related) (fun _ _ h => h)
+ have full := narrowed.wpDep (fun s t h =>
+ ⟨prepare_layout_ok s h.related.left h.leftReads,
+ prepare_layout_ok t h.related.right h.rightReads⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact hp.related.of_stable ha.stable hb.stable
+
+theorem code_rel : RelCT isa PrepareRelated code Related := prepare_rel.seq calls_rel
+
+end VG.Proof.Argon2.X86_64.AddressCalls
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeader.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeader.lean
new file mode 100644
index 000000000..3d8cc32cb
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeader.lean
@@ -0,0 +1,100 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeaderWords
+import VerifiedGarbage.Proof.Framework.X86_64.Inline
+
+/-! Compose the seven input fields, preserving frame reads across every write. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressHeader
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressHeader
+
+def value (s : State) (i : Nat) : Addr :=
+ if i = 1 then s.gpr .rbx else if i = 2 then s.gpr .r14
+ else s.mem.readW (off (s.gpr .rbp) (frameOffset i)) 64
+
+def headerMem (s : State) (p : Addr) : Nat → Mem
+ | 0 => s.mem
+ | n + 1 => (headerMem s p n).writeW (off p (8 * n)) (value s n)
+
+theorem field_ok (s : State) (i : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) (frameOffset i)) 8)
+ (hw : InRegions s.wr (off (s.gpr .rdi) (8 * i)) 8) :
+ WP isa (.block (field i)) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rdi) (8 * i)) (value s i) ∧
+ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ unfold field value
+ by_cases one : i = 1
+ · simp only [one, ite_true]
+ refine (registerWord_ok s 1 .rbx (one ▸ hw)).mono ?_
+ rintro t ⟨mem, regs, rd, wr, mx⟩
+ exact ⟨mem, ⟨fun r _ => congrFun regs r, rd, wr⟩, mx⟩
+ · simp only [one, ite_false]
+ by_cases two : i = 2
+ · simp only [two, ite_true]
+ refine (registerWord_ok s 2 .r14 (two ▸ hw)).mono ?_
+ rintro t ⟨mem, regs, rd, wr, mx⟩
+ exact ⟨mem, ⟨fun r _ => congrFun regs r, rd, wr⟩, mx⟩
+ · simp only [two, ite_false]
+ refine (frameWord_ok s i (frameOffset i) hr hw).mono ?_
+ rintro t ⟨mem, regs, rd, wr, mx⟩
+ exact ⟨mem, ⟨regs, rd, wr⟩, mx⟩
+
+theorem offset_bound : ∀ i < 7, frameOffset i + 8 ≤ 272 := by decide +kernel
+
+theorem offset_read (s : State) (i : Nat)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8) :
+ InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) (frameOffset i)) 8 := by
+ apply reads
+ unfold frameOffset
+ split <;> [simp; skip]
+ split <;> [simp; skip]
+ split <;> [simp; skip]
+ split <;> simp
+
+theorem value_kept {s t : State} (keeps : CopyKeeps s t)
+ (hf : Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem)
+ (sep : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩)
+ (i : Nat) (hi : i < 7) : value t i = value s i := by
+ unfold value
+ rw [keeps.1 .rbx (by decide), keeps.1 .r14 (by decide), keeps.1 .rbp (by decide)]
+ have read : t.mem.readW (off (s.gpr .rbp) (frameOffset i)) 64 =
+ s.mem.readW (off (s.gpr .rbp) (frameOffset i)) 64 :=
+ hf.readW (r := ⟨s.gpr .rbp, 272⟩)
+ (Offset.contains_base _ (offset_bound i hi)
+ (Nat.lt_of_le_of_lt (Nat.le_trans (Nat.le_add_right _ _) (offset_bound i hi)) (by decide)))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r; exact sep) (by decide)
+ rw [read]
+
+theorem prefix_ok (n : Nat) (hn : n ≤ 7) (s : State)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr)
+ (sep : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩) :
+ WP isa (.block (fields n)) s fun t =>
+ t.mem = headerMem s (s.gpr .rdi) n ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ induction n with
+ | zero => exact WP.block_nil ⟨rfl, Frame.refl _ _, CopyKeeps.refl s, rfl⟩
+ | succ n ih =>
+ simp only [fields, List.range_succ, List.flatMap_append, List.flatMap_cons,
+ List.flatMap_nil, List.append_nil]
+ apply WP.block_append
+ refine (ih (by omega)).mono ?_
+ rintro a ⟨mem, frame, keeps, mx⟩
+ have dest : a.gpr .rdi = s.gpr .rdi := keeps.1 .rdi (by decide)
+ have read : InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) (frameOffset n)) 8 := by
+ rw [keeps.2.1, keeps.2.2, keeps.1 .rbp (by decide)]
+ exact offset_read s n reads
+ have writable : InRegions a.wr (off (a.gpr .rdi) (8 * n)) 8 := by
+ rw [dest, keeps.2.2]
+ exact write _ _ ⟨⟨s.gpr .rdi, 1024⟩, by simp,
+ Offset.contains_base _ (d := 8 * n) (n := 8) (k := 1024) (by omega) (by omega)⟩
+ refine (field_ok a n read writable).mono ?_
+ rintro t ⟨mem', keeps', mx'⟩
+ have value' := value_kept keeps frame sep n (by omega)
+ refine ⟨?_, ?_, keeps.trans keeps', mx'.trans mx⟩
+ · rw [mem', dest, value', mem]
+ rfl
+ · rw [mem', dest]
+ exact frame.writeW (r := ⟨s.gpr .rdi, 1024⟩) (by simp) _
+ (Offset.contains_base _ (by omega) (by omega))
+
+end VG.Proof.Argon2.X86_64.AddressHeader
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderCorrect.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderCorrect.lean
new file mode 100644
index 000000000..9bd6730f4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderCorrect.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeader
+import VerifiedGarbage.Proof.Argon2.AddressInput
+
+/-! The prepared input agrees with RFC 9106's seven public address words. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressHeader
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.AddressHeader
+
+def input (s : State) : Block :=
+ zeroBlock |>.set 0 (value s 0) |>.set 1 (value s 1) |>.set 2 (value s 2)
+ |>.set 3 (value s 3) |>.set 4 (value s 4) |>.set 5 (value s 5) |>.set 6 (value s 6)
+
+theorem headerMem_block (s : State) (p : Addr) (zero : blockAt s.mem p = zeroBlock) :
+ blockAt (headerMem s p 7) p = input s := by
+ rw [headerMem, blockAt_write_nat _ p 6 (by decide),
+ headerMem, blockAt_write_nat _ p 5 (by decide),
+ headerMem, blockAt_write_nat _ p 4 (by decide),
+ headerMem, blockAt_write_nat _ p 3 (by decide),
+ headerMem, blockAt_write_nat _ p 2 (by decide),
+ headerMem, blockAt_write_nat _ p 1 (by decide),
+ headerMem, blockAt_write_nat _ p 0 (by decide), headerMem, zero]
+ rfl
+
+theorem code_ok (s : State)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr)
+ (sep : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩)
+ (zero : blockAt s.mem (s.gpr .rdi) = zeroBlock) :
+ WP isa code s fun t => blockAt t.mem (s.gpr .rdi) = input s ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ refine (prefix_ok 7 (by decide) s reads write sep).mono ?_
+ rintro t ⟨mem, frame, keeps, mx⟩
+ exact ⟨by rw [mem]; exact headerMem_block s _ zero, frame, keeps, mx⟩
+
+structure Words (p : Params) (pass lane slice counter : Nat) (s : State) : Prop where
+ passWord : s.mem.readW (off (s.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass
+ laneWord : s.gpr .rbx = BitVec.ofNat 64 lane
+ sliceWord : s.gpr .r14 = BitVec.ofNat 64 slice
+ blocksWord : s.mem.readW (off (s.gpr .rbp) 240) 64 = BitVec.ofNat 64 p.blocks
+ passesWord : s.mem.readW (off (s.gpr .rbp) 72) 64 = BitVec.ofNat 64 p.passes
+ variantWord : s.mem.readW (off (s.gpr .rbp) 112) 64 = BitVec.ofNat 64 p.variant.code
+ counterWord : s.mem.readW (off (s.gpr .rbp) 8) 64 = BitVec.ofNat 64 counter
+
+theorem input_spec (p : Params) (pass lane slice counter : Nat) (s : State)
+ (h : Words p pass lane slice counter s) :
+ input s = Proof.Argon2.addressInput p pass lane slice counter := by
+ simp (config := {decide := true}) only [input, value, frameOffset,
+ ite_true, ite_false, h.passWord, h.laneWord, h.sliceWord, h.blocksWord,
+ h.passesWord, h.variantWord, h.counterWord, Proof.Argon2.addressInput]
+
+theorem code_spec_ok (p : Params) (pass lane slice counter : Nat) (s : State)
+ (reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8)
+ (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr)
+ (sep : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩)
+ (zero : blockAt s.mem (s.gpr .rdi) = zeroBlock)
+ (words : Words p pass lane slice counter s) :
+ WP isa code s fun t =>
+ blockAt t.mem (s.gpr .rdi) = Proof.Argon2.addressInput p pass lane slice counter ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr :=
+ (code_ok s reads write sep zero).mono (fun _ h =>
+ ⟨h.1.trans (input_spec p pass lane slice counter s words), h.2⟩)
+
+end VG.Proof.Argon2.X86_64.AddressHeader
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderLit.lean
new file mode 100644
index 000000000..8a55ac4cc
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressHeader
+
+/-! Checked literal of the independent-address input header. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.AddressHeader.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderWords.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderWords.lean
new file mode 100644
index 000000000..ed964636e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressHeaderWords.lean
@@ -0,0 +1,38 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressHeader
+import VerifiedGarbage.Proof.Argon2.X86_64.BlockStore
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! Short independent-address input header writes. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressHeader
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressHeader
+
+theorem registerWord_ok (s : State) (i : Nat) (r : Reg)
+ (hw : InRegions s.wr (off (s.gpr .rdi) (8 * i)) 8) :
+ WP isa (.block (registerWord i r)) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rdi) (8 * i)) (s.gpr r) ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [registerWord, runBlock_cons, runStep_some, runBlock_nil, exec,
+ State.store64, ea_at, hw, ite_true, Option.some.injEq, exists_eq_left']
+ exact ⟨trivial, trivial, trivial, trivial, trivial⟩
+
+theorem frameWord_ok (s : State) (i offset : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) offset) 8)
+ (hw : InRegions s.wr (off (s.gpr .rdi) (8 * i)) 8) :
+ WP isa (.block (frameWord i offset)) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rdi) (8 * i))
+ (s.mem.readW (off (s.gpr .rbp) offset) 64) ∧
+ (∀ r, r ≠ .rax → t.gpr r = s.gpr r) ∧
+ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [frameWord, runBlock_cons, runStep_some, runBlock_nil, exec,
+ readSrc, State.load64, State.store64, ea_at, hr, hw,
+ RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ reduceCtorEq, ite_true, ite_false, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_, trivial, trivial, rfl⟩
+ intro r hr
+ simp only [hr, ite_false]
+
+end VG.Proof.Argon2.X86_64.AddressHeader
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressInputCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressInputCT.lean
new file mode 100644
index 000000000..08d706d40
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressInputCT.lean
@@ -0,0 +1,26 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ClearBlockLit
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeaderLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Clearing and header preparation visit fixed offsets of public pointers. -/
+
+namespace VG.Proof.Argon2.X86_64
+
+open VG VG.X86_64
+
+theorem ClearBlock.code_rel : RelCT isa (fun s t => s.gpr .rdi = t.gpr .rdi)
+ Impl.Argon2.X86_64.ClearBlock.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rdi])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem AddressHeader.code_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rdi, .rbp], s.gpr r = t.gpr r)
+ Impl.Argon2.X86_64.AddressHeader.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rdi, .rbp])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressMode.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressMode.lean
new file mode 100644
index 000000000..80bf96752
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressMode.lean
@@ -0,0 +1,73 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressModeSteps
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapState
+
+/-! The segment mode is exactly the reviewed Argon2d/i/id addressing predicate. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressMode
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressMode
+
+def value (s : State) : Addr :=
+ let kind := s.mem.readW (off (s.gpr .rbp) 112) 64
+ let pass := s.mem.readW (off (s.gpr .rbp) 0) 64
+ (Divide.mask (decide (kind = 1)) ||| ((Divide.mask (decide (kind = 2)) &&&
+ Divide.mask (decide (pass = 0#64))) &&& Divide.mask (decide ((s.gpr .r14).toNat < 2)))) &&& 1
+
+def changed : List Reg := [.rax, .r8, .r9, .r10, .r11]
+
+theorem code_ok (s : State)
+ (kindRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 112) 8)
+ (passRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8) :
+ WP isa code s fun t => t.gpr .r10 = value s ∧ Divide.Keeps changed s t := by
+ unfold code
+ refine WP.seq ((kind_ok s kindRead).mono ?_)
+ rintro a ⟨i, id, ka⟩
+ have read : InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) 0) 8 := by
+ rw [ka.rd, ka.wr, ka.regs .rbp (by decide)]; exact passRead
+ refine WP.seq ((pass_ok a read).mono ?_)
+ rintro b ⟨zero, kb⟩
+ refine (slice_ok b).mono ?_
+ rintro t ⟨result, kt⟩
+ refine ⟨?_, ((ka.mono (by decide)).trans (kb.mono (by decide))).trans (kt.mono (by decide))⟩
+ rw [result, kb.regs .r10 (by decide), i, kb.regs .r8 (by decide), id, zero,
+ ka.mem, ka.regs .rbp (by decide), kb.regs .r14 (by decide), ka.regs .r14 (by decide)]
+ rfl
+
+theorem masks : ∀ a b c d : Bool,
+ (Divide.mask a ||| ((Divide.mask b &&& Divide.mask c) &&& Divide.mask d)) &&& 1 =
+ (BitVec.ofBool (a || (b && c && d))).setWidth 64 := by decide +kernel
+
+theorem variants : ∀ v : Spec.Argon2.Variant, ∀ a b : Bool,
+ (decide (BitVec.ofNat 64 v.code = (1 : Addr)) ||
+ (decide (BitVec.ofNat 64 v.code = (2 : Addr)) && a && b)) =
+ ((v == .i) || ((v == .id) && a && b)) := by
+ intro v
+ cases v <;> decide +kernel
+
+theorem value_spec (s : State) (p : Spec.Argon2.Params) (pass slice : Nat)
+ (kindWord : s.mem.readW (off (s.gpr .rbp) 112) 64 = BitVec.ofNat 64 p.variant.code)
+ (passWord : s.mem.readW (off (s.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass)
+ (sliceWord : s.gpr .r14 = BitVec.ofNat 64 slice)
+ (passBound : pass < 2 ^ 64) (sliceBound : slice < 2 ^ 64) :
+ value s = (BitVec.ofBool (Spec.Argon2.independent p pass slice)).setWidth 64 := by
+ have passZero : BitVec.ofNat 64 pass = 0#64 ↔ pass = 0 := ReferenceMap.word_zero pass passBound
+ unfold value
+ rw [kindWord, passWord, sliceWord, masks,
+ ReferenceMap.word_nat slice sliceBound]
+ simp only [passZero]
+ unfold Spec.Argon2.independent
+ rw [variants, Bool.beq_eq_decide_eq pass 0]
+
+theorem code_spec_ok (s : State) (p : Spec.Argon2.Params) (pass slice : Nat)
+ (kindRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 112) 8)
+ (passRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8)
+ (kindWord : s.mem.readW (off (s.gpr .rbp) 112) 64 = BitVec.ofNat 64 p.variant.code)
+ (passWord : s.mem.readW (off (s.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass)
+ (sliceWord : s.gpr .r14 = BitVec.ofNat 64 slice)
+ (passBound : pass < 2 ^ 64) (sliceBound : slice < 2 ^ 64) :
+ WP isa code s fun t => t.gpr .r10 =
+ (BitVec.ofBool (Spec.Argon2.independent p pass slice)).setWidth 64 ∧ Divide.Keeps changed s t :=
+ (code_ok s kindRead passRead).mono (fun _ h =>
+ ⟨h.1.trans (value_spec s p pass slice kindWord passWord sliceWord passBound sliceBound), h.2⟩)
+
+end VG.Proof.Argon2.X86_64.AddressMode
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeCT.lean
new file mode 100644
index 000000000..226a8e8c4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeCT.lean
@@ -0,0 +1,18 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressModeLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Mode selection has a fixed trace at the public frame base. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressMode
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressMode
+
+theorem code_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp) code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.AddressMode
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeLit.lean
new file mode 100644
index 000000000..f5791234d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressMode
+
+/-! Checked literal of the segment addressing-mode computation. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.AddressMode.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeSteps.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeSteps.lean
new file mode 100644
index 000000000..6b7089bcc
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/AddressModeSteps.lean
@@ -0,0 +1,76 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.AddressMode
+import VerifiedGarbage.Proof.Argon2.X86_64.Memory
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! Short mask computations for the public addressing-mode predicate. -/
+
+namespace VG.Proof.Argon2.X86_64.AddressMode
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.AddressMode
+
+theorem zero_nat (x : Addr) : x.toNat < 1 ↔ x = 0#64 := by
+ constructor
+ · intro h; exact BitVec.eq_of_toNat_eq (Nat.lt_one_iff.mp h)
+ · intro h; rw [h]; decide
+
+theorem xor_nat (x y : Addr) : (x ^^^ y).toNat < 1 ↔ x = y := by
+ rw [zero_nat, BitVec.xor_eq_zero_iff]
+
+theorem kind_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 112) 8) :
+ WP isa (.block kind) s fun t =>
+ t.gpr .r10 = Divide.mask (decide (s.mem.readW (off (s.gpr .rbp) 112) 64 = 1)) ∧
+ t.gpr .r8 = Divide.mask (decide (s.mem.readW (off (s.gpr .rbp) 112) 64 = 2)) ∧
+ Divide.Keeps [.rax, .r10, .r8] s t := by
+ apply WP.of_runBlock
+ simp only [kind, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, hr, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ RegUpd.cf_arithFlags, reduceCtorEq, ite_true, ite_false,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ show BitVec.signExtend 64 (2 : BitVec 32) = (2 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq,
+ exists_eq_left', Divide.sbb_mask, show (1 : Addr).toNat = 1 from rfl, xor_nat]
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem pass_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8) :
+ WP isa (.block pass) s fun t =>
+ t.gpr .r9 = Divide.mask (decide (s.mem.readW (off (s.gpr .rbp) 0) 64 = 0#64)) ∧
+ Divide.Keeps [.r9] s t := by
+ apply WP.of_runBlock
+ simp only [pass, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, hr, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ RegUpd.cf_arithFlags, ite_true,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq,
+ exists_eq_left', Divide.sbb_mask, show (1 : Addr).toNat = 1 from rfl, zero_nat]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem slice_ok (s : State) : WP isa (.block slice) s fun t =>
+ t.gpr .r10 = (s.gpr .r10 ||| ((s.gpr .r8 &&& s.gpr .r9) &&&
+ Divide.mask (decide ((s.gpr .r14).toNat < 2)))) &&& 1 ∧
+ Divide.Keeps [.r8, .r11, .r10] s t := by
+ apply WP.of_runBlock
+ simp only [slice, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, RegUpd.cf_arithFlags,
+ reduceCtorEq, ite_true, ite_false,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ show BitVec.signExtend 64 (2 : BitVec 32) = (2 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq,
+ exists_eq_left', Divide.sbb_mask, show (2 : Addr).toNat = 2 from rfl]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.AddressMode
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddress.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddress.lean
new file mode 100644
index 000000000..66b63e638
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddress.lean
@@ -0,0 +1,81 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.BlockAddress
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitScale
+import VerifiedGarbage.Proof.Framework.X86_64.Abi
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Fault-free matrix pointer calculation, with no memory accesses. -/
+
+namespace VG.Proof.Argon2.X86_64.BlockAddress
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.BlockAddress
+
+theorem flatten_ok (s : State) : WP isa (.block flatten) s fun t =>
+ t.gpr .rax = s.gpr .rax * s.gpr .r12 + s.gpr .rcx ∧
+ Divide.Keeps [.rax, .rdx] s t := by
+ apply WP.of_runBlock
+ simp only [flatten, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ execMul, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_setFlags, RegUpd.gpr_arithFlags,
+ reduceCtorEq, ite_true, ite_false, Option.bind_some, Option.some.injEq,
+ exists_eq_left', BitVec.ofNat_mul, BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_setFlags, RegUpd.gpr_arithFlags,
+ hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem scale_ok (s : State) : WP isa (.block scale) s fun t =>
+ t.gpr .rax = s.gpr .rax * 1024 ∧ Divide.Keeps [.rax] s t := by
+ refine WP.mono_mx (by decide +kernel) (MemoryInit.scale_ok s .rax 10) ?_
+ intro t h mx
+ refine ⟨h.value, ?_, h.mem, h.rd, h.wr, mx⟩
+ intro r hr
+ exact h.other r (by simpa only [List.mem_cons, List.not_mem_nil, or_false] using hr)
+
+theorem base_ok (s : State) : WP isa (.block [.alu .add .rax (.reg .r8)]) s fun t =>
+ t.gpr .rax = s.gpr .rax + s.gpr .r8 ∧ Divide.Keeps [.rax] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, Option.bind_some,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨by simp only [ite_true], ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ exact ite_eq_right hr
+ all_goals rfl
+
+theorem code_ok (s : State) : WP isa code s fun t =>
+ t.gpr .rax = (s.gpr .rax * s.gpr .r12 + s.gpr .rcx) * 1024 + s.gpr .r8 ∧
+ Divide.Keeps [.rax, .rdx] s t := by
+ unfold code
+ refine WP.seq ((flatten_ok s).mono ?_)
+ rintro a ⟨flat, ka⟩
+ refine WP.seq ((scale_ok a).mono ?_)
+ rintro b ⟨scaled, kb⟩
+ refine (base_ok b).mono ?_
+ rintro t ⟨result, kt⟩
+ refine ⟨?_, ka.trans ((kb.mono (by simp)).trans (kt.mono (by simp)))⟩
+ rw [result, scaled, flat, kb.regs .r8 (by decide), ka.regs .r8 (by decide)]
+
+theorem code_nat_ok (s : State) (lane column q : Nat)
+ (hl : s.gpr .rax = BitVec.ofNat 64 lane)
+ (hc : s.gpr .rcx = BitVec.ofNat 64 column)
+ (hq : s.gpr .r12 = BitVec.ofNat 64 q) :
+ WP isa code s fun t =>
+ t.gpr .rax = s.gpr .r8 + BitVec.ofNat 64 ((lane * q + column) * 1024) ∧
+ Divide.Keeps [.rax, .rdx] s t := by
+ refine (code_ok s).mono ?_
+ rintro t ⟨h, k⟩
+ refine ⟨?_, k⟩
+ rw [h, hl, hc, hq, ← BitVec.ofNat_mul, ← BitVec.ofNat_add]
+ change BitVec.ofNat 64 (lane * q + column) * BitVec.ofNat 64 1024 + s.gpr .r8 = _
+ rw [← BitVec.ofNat_mul, BitVec.add_comm]
+
+theorem code_secret_rel : RelCT isa (fun _ _ => True) code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.BlockAddress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddressLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddressLit.lean
new file mode 100644
index 000000000..af28ce7cd
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockAddressLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.BlockAddress
+
+/-! A checked literal for lane-major matrix block addressing. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.BlockAddress.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockStore.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockStore.lean
new file mode 100644
index 000000000..1d7af7460
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/BlockStore.lean
@@ -0,0 +1,28 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.Initialize
+
+/-! A single matrix or scratch block word write as a vector update. -/
+
+namespace VG.Proof.Argon2.X86_64
+
+open VG VG.Spec.Argon2
+
+theorem blockAt_write (m : Mem) (p : Addr) (i : Fin 128) (v : Word) :
+ blockAt (m.writeW (off p (8 * i.val)) v) p = (blockAt m p).set i v := by
+ apply Vector.ext
+ intro j hj
+ simp only [blockAt, Vector.getElem_ofFn, Vector.getElem_set]
+ by_cases eq : i.val = j
+ · subst j
+ simp only [ite_true]
+ change (m.writeW (off p (8 * i.val)) v).readW (off p (8 * i.val)) 64 = v
+ exact Mem.readW_writeW_self64 _ _ _
+ · simp only [eq, ite_false]
+ change (m.writeW (off p (8 * i.val)) v).readW (off p (8 * j)) 64 =
+ m.readW (off p (8 * j)) 64
+ exact Mem.readW_writeW_sep (Offset.sep p (by omega) (by omega) (by omega)) (by decide)
+
+theorem blockAt_write_nat (m : Mem) (p : Addr) (i : Nat) (hi : i < 128) (v : Word) :
+ blockAt (m.writeW (off p (8 * i)) v) p = (blockAt m p).set i v hi :=
+ blockAt_write m p ⟨i, hi⟩ v
+
+end VG.Proof.Argon2.X86_64
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlock.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlock.lean
new file mode 100644
index 000000000..c08198c81
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlock.lean
@@ -0,0 +1,78 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ClearBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitClear
+import VerifiedGarbage.Proof.Argon2.X86_64.Initialize
+import VerifiedGarbage.Proof.Framework.X86_64.Inline
+
+/-! Zero every word, preserving the enclosing loop's registers and memory. -/
+
+namespace VG.Proof.Argon2.X86_64.ClearBlock
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.ClearBlock
+
+theorem word_ok (s : State) (i : Nat)
+ (hw : InRegions s.wr (off (s.gpr .rdi) (8 * i)) 8) :
+ WP isa (.block (Impl.Argon2.X86_64.ClearBlock.word i)) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rdi) (8 * i)) (s.gpr .rax) ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.ClearBlock.word, runBlock_cons, runStep_some, runBlock_nil,
+ exec, State.store64, ea_at, hw, ite_true, Option.some.injEq, exists_eq_left']
+ exact ⟨trivial, trivial, trivial, trivial, trivial⟩
+
+theorem prefix_ok (n : Nat) (hn : n ≤ 128) (s : State) (zero : s.gpr .rax = 0)
+ (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr) :
+ WP isa (.block (words n)) s fun t =>
+ t.mem = MemoryInit.clearMem s.mem (s.gpr .rdi) n ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ induction n with
+ | zero => exact WP.block_nil ⟨rfl, rfl, rfl, rfl, rfl⟩
+ | succ n ih =>
+ simp only [words, List.range_succ, List.flatMap_append, List.flatMap_cons,
+ List.flatMap_nil, List.append_nil]
+ apply WP.block_append
+ refine (ih (by omega)).mono ?_
+ rintro a ⟨mem, regs, rd, wr, mx⟩
+ have hw : InRegions a.wr (off (a.gpr .rdi) (8 * n)) 8 := by
+ rw [regs, wr]
+ exact write _ _ ⟨⟨s.gpr .rdi, 1024⟩, by simp,
+ Offset.contains_base _ (d := 8 * n) (n := 8) (k := 1024) (by omega) (by omega)⟩
+ refine (word_ok a n hw).mono ?_
+ rintro t ⟨mem', regs', rd', wr', mx'⟩
+ refine ⟨?_, regs'.trans regs, rd'.trans rd, wr'.trans wr, mx'.trans mx⟩
+ rw [mem', regs, zero, mem]
+ rfl
+
+theorem zero_ok (s : State) : WP isa (.block [.mov .rax (.imm 0)]) s fun t =>
+ t.gpr .rax = 0 ∧ (∀ r, r ≠ .rax → t.gpr r = s.gpr r) ∧
+ t.mem = s.mem ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨rfl, ?_, rfl, rfl, rfl, rfl⟩
+ intro r hr
+ exact ite_eq_right hr
+
+theorem code_ok (s : State) (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr) :
+ WP isa code s fun t => blockAt t.mem (s.gpr .rdi) = zeroBlock ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ unfold code
+ refine WP.seq ((zero_ok s).mono ?_)
+ rintro a ⟨zero, regs, mem, rd, wr, mx⟩
+ have dest : a.gpr .rdi = s.gpr .rdi := regs .rdi (by decide)
+ have write' : Covers [⟨a.gpr .rdi, 1024⟩] a.wr := by rw [dest, wr]; exact write
+ refine (prefix_ok 128 (by decide) a zero write').mono ?_
+ rintro t ⟨mem', regs', rd', wr', mx'⟩
+ have cleared : t.mem = MemoryInit.clearMem s.mem (s.gpr .rdi) 128 := by
+ rw [mem', mem, dest]
+ refine ⟨?_, ?_, ⟨fun r hr => (congrFun regs' r).trans (regs r hr), rd'.trans rd, wr'.trans wr⟩,
+ mx'.trans mx⟩
+ · rw [cleared]
+ apply Vector.ext
+ intro i hi
+ change (blockAt _ _)[(⟨i, hi⟩ : Fin 128)] = zeroBlock[(⟨i, hi⟩ : Fin 128)]
+ rw [blockAt_get, MemoryInit.clearMem_word _ _ 128 i (by decide) hi]
+ simp only [zeroBlock, Fin.getElem_fin, Vector.getElem_replicate]
+ · rw [cleared]
+ exact MemoryInit.clearMem_frame _ _ 128 (by decide)
+
+end VG.Proof.Argon2.X86_64.ClearBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlockLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlockLit.lean
new file mode 100644
index 000000000..0fd6d8f80
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ClearBlockLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.ClearBlock
+
+/-! Checked literal of a complete address-generation block clear. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.ClearBlock.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWord.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWord.lean
new file mode 100644
index 000000000..4125b3273
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWord.lean
@@ -0,0 +1,68 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWordPointer
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelSpec
+
+/-! Select the specified secret random word from the public previous-cell address. -/
+
+namespace VG.Proof.Argon2.X86_64.DependentWord
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.DependentWord
+
+theorem read_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (s.gpr .rax) 8) :
+ WP isa (.block Impl.Argon2.X86_64.DependentWord.read) s fun t => t.gpr .rdi = s.mem.readW (s.gpr .rax) 64 ∧ Divide.Keeps [.rdi] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.DependentWord.read, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, BitVec.add_zero, hr, Option.map_some,
+ Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+ all_goals rfl
+
+theorem code_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : FillKernel.Ready p pass lane slice index s) : WP isa code s fun t =>
+ t.gpr .rdi = (blockAt s.mem (FillKernel.previous s p lane slice index))[0] ∧
+ Divide.Keeps ReferenceMap.changed s t := by
+ unfold code
+ refine WP.seq ((pointer_ok s p pass lane slice index h).mono ?_)
+ rintro a ⟨pointer, keeps⟩
+ have bound := Proof.Argon2.previous_column_lt p h.bounds.lanesPositive h.bounds.memoryMinimum
+ (slice * p.segmentLen + index)
+ have cover := h.layout.cell_cover h.bounds.lanesPositive h.bounds.laneBound bound
+ have hr : InRegions (a.rd ++ a.wr) (a.gpr .rax) 8 := by
+ rw [keeps.rd, keeps.wr, pointer]
+ have contains : (⟨FillKernel.previous s p lane slice index, 1024⟩ : Region).Contains
+ (FillKernel.previous s p lane slice index) 8 :=
+ by simpa only [BitVec.add_zero] using (Offset.contains_base
+ (FillKernel.previous s p lane slice index) (d := 0) (n := 8) (k := 1024) (by decide) (by decide))
+ obtain ⟨r, hr, hc⟩ := cover _ _ ⟨⟨FillKernel.previous s p lane slice index, 1024⟩,
+ by simp [FillKernel.previous, FillKernel.previousColumn, FillKernel.currentColumn], contains⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ refine (read_ok a hr).mono ?_
+ rintro t ⟨random, tail⟩
+ refine ⟨?_, keeps.trans (tail.mono (by decide))⟩
+ rw [random, pointer, keeps.mem]
+ change s.mem.readW _ 64 = (blockAt _ _)[(⟨0, by decide⟩ : Fin 128)]
+ rw [blockAt_get]
+ change s.mem.readW _ 64 = s.mem.readW (_ + 0#64) 64
+ rw [BitVec.add_zero]
+
+theorem code_spec_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : FillKernel.Ready p pass lane slice index s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (dependent : independent p pass slice = false) : WP isa code s fun t =>
+ t.gpr .rdi = Proof.Argon2.FillStep.random p pass lane slice index state.memory ∧
+ Divide.Keeps ReferenceMap.changed s t := by
+ refine (code_ok s p pass lane slice index h).mono ?_
+ rintro t ⟨random, keeps⟩
+ have bound := Proof.Argon2.previous_cell_lt p h.bounds.lanesPositive h.bounds.memoryMinimum
+ h.bounds.laneBound (column := FillKernel.currentColumn p slice index)
+ have block := represented.block (FillKernel.previousIndex p lane slice index) bound
+ change blockAt s.mem (FillKernel.previous s p lane slice index) = _ at block
+ rw [block] at random
+ refine ⟨?_, keeps⟩
+ simpa only [Proof.Argon2.FillStep.random, dependent, Bool.false_eq_true, ite_false, FillKernel.previousIndex, FillKernel.previousColumn,
+ FillKernel.currentColumn] using random
+
+end VG.Proof.Argon2.X86_64.DependentWord
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordCT.lean
new file mode 100644
index 000000000..408e40078
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordCT.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWord
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWordLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! The previous cell is read at an address determined by public parameters. -/
+
+namespace VG.Proof.Argon2.X86_64.DependentWord
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.DependentWord
+
+structure Related (p : Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ left : FillKernel.Ready p pass lane slice index s
+ right : FillKernel.Ready p pass lane slice index t
+ bases : s.gpr .rbp = t.gpr .rbp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+
+theorem pointer_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rbp, .r12, .r13, .r14, .r15], s.gpr r = t.gpr r)
+ pointer (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp, .r12, .r13, .r14, .r15])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+theorem read_rel : RelCT isa (fun s t => s.gpr .rax = t.gpr .rax) (.block Impl.Argon2.X86_64.DependentWord.read) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rax])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem code_rel (p : Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) code (fun _ _ => True) := by
+ have trace := pointer_rel.mono (P' := Related p pass lane slice index) (by
+ intro s t h r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact h.bases
+ · exact h.left.position.laneLength.trans h.right.position.laneLength.symm
+ · exact h.left.position.segmentLength.trans h.right.position.segmentLength.symm
+ · exact h.left.position.slice.trans h.right.position.slice.symm
+ · exact h.left.position.index.trans h.right.position.index.symm) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨pointer_ok s p pass lane slice index h.left, pointer_ok t p pass lane slice index h.right⟩)
+ have publicTrace : RelCT isa (Related p pass lane slice index) pointer
+ (fun s t => s.gpr .rax = t.gpr .rax) := full.mono (fun _ _ h => h) (by
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨pa, _⟩, ⟨pb, _⟩⟩ := h
+ have equal : FillKernel.previous s p lane slice index = FillKernel.previous t p lane slice index := by
+ unfold FillKernel.previous; rw [hp.matrices]
+ exact pa.trans (equal.trans pb.symm))
+ exact publicTrace.seq read_rel
+
+end VG.Proof.Argon2.X86_64.DependentWord
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordLit.lean
new file mode 100644
index 000000000..e0d6657f7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.DependentWord
+
+/-! Checked literal of the public predecessor-address computation. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.DependentWord.pointer
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordPointer.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordPointer.lean
new file mode 100644
index 000000000..222bc410b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordPointer.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.DependentWord
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelPrepare
+
+/-! The data-dependent word's address is the specification's cyclic predecessor. -/
+
+namespace VG.Proof.Argon2.X86_64.DependentWord
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.DependentWord
+
+theorem args_ok (s : State) : WP isa (.block args) s fun t =>
+ t.gpr .rcx = s.gpr .rdi ∧ t.gpr .rax = s.gpr .rbx ∧ Divide.Keeps [.rcx, .rax] s t := by
+ apply WP.of_runBlock
+ simp only [args, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ RegUpd.gpr_setReg, reduceCtorEq, ite_true, ite_false, Option.map_some,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem pointer_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : FillKernel.Ready p pass lane slice index s) : WP isa pointer s fun t =>
+ t.gpr .rax = FillKernel.previous s p lane slice index ∧ Divide.Keeps ReferenceMap.changed s t := by
+ unfold pointer
+ refine WP.seq ((FillKernel.load_ok s .r8 232 (h.layout.frameRead 232 (by simp))).mono ?_)
+ rintro a ⟨base, ka⟩
+ have k : Divide.Keeps ReferenceMap.changed s a := ka.mono (by decide)
+ have pos := h.position.of_keeps k
+ have columnBound := Proof.Argon2.column_lt p h.bounds.lanesPositive h.bounds.sliceBound h.bounds.indexBound
+ have segment := Proof.Argon2.segmentLen_ge_two p h.bounds.lanesPositive h.bounds.memoryMinimum
+ have q := Proof.Argon2.laneLen_segments p h.bounds.lanesPositive
+ refine WP.seq ((FillColumn.code_nat_ok a slice p.segmentLen index p.laneLen
+ pos.slice pos.segmentLength pos.index pos.laneLength (by omega)
+ (Nat.lt_trans h.bounds.laneLength_bound (by decide)) columnBound).mono ?_)
+ rintro b ⟨_, prev, kb⟩
+ refine WP.seq ((args_ok b).mono ?_)
+ rintro c ⟨col, laneReg, kc⟩
+ have col' := col.trans prev
+ have lane' : c.gpr .rax = BitVec.ofNat 64 lane := laneReg.trans ((kb.regs .rbx (by decide)).trans pos.current)
+ have length : c.gpr .r12 = BitVec.ofNat 64 p.laneLen :=
+ (kc.regs .r12 (by decide)).trans ((kb.regs .r12 (by decide)).trans pos.laneLength)
+ refine (BlockAddress.code_nat_ok c lane
+ ((slice * p.segmentLen + index + p.laneLen - 1) % p.laneLen) p.laneLen lane' col' length).mono ?_
+ rintro t ⟨address, kt⟩
+ refine ⟨?_, ((k.trans (kb.mono (by decide))).trans (kc.mono (by decide))).trans (kt.mono (by decide))⟩
+ rw [address, kc.regs .r8 (by decide), kb.regs .r8 (by decide), base]
+ rfl
+
+end VG.Proof.Argon2.X86_64.DependentWord
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordState.lean
new file mode 100644
index 000000000..33d7648fe
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DependentWordState.lean
@@ -0,0 +1,25 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWord
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelStable
+
+/-! The dependent source hands the filling step its random word and unchanged matrix. -/
+
+namespace VG.Proof.Argon2.X86_64.DependentWord
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.DependentWord
+
+theorem state_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : FillKernel.Ready p pass lane slice index s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (dependent : independent p pass slice = false) : WP isa code s fun t =>
+ t.gpr .rdi = Proof.Argon2.FillStep.random p pass lane slice index state.memory ∧
+ FillKernel.Ready p pass lane slice index t ∧
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory ∧
+ Divide.Keeps ReferenceMap.changed s t := by
+ refine (code_spec_ok s p pass lane slice index h state represented dependent).mono ?_
+ rintro t ⟨random, keeps⟩
+ refine ⟨random, h.of_keeps keeps, ?_, keeps⟩
+ have base : FillKernel.matrix t = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ rw [keeps.mem, base]; exact represented
+
+end VG.Proof.Argon2.X86_64.DependentWord
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAbi.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAbi.lean
new file mode 100644
index 000000000..ac3cb6eff
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAbi.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Framework.Contract
+import VerifiedGarbage.Proof.Framework.X86_64.Call
+import VerifiedGarbage.Spec.Argon2.Contract
+import VerifiedGarbage.TCB.X86_64.Target
+
+/-! Decode the reviewed System V contract without requiring normalized upper bits. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def abiWord (s : State) (d : Nat) : Addr := s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 d) 64
+def abiParams (s : State) : Spec.Argon2.Params := Spec.Argon2.params
+ ((s.gpr .rdi).setWidth 32).toNat ((s.gpr .r9).setWidth 32).toNat
+ ((abiWord s 8).setWidth 32).toNat ((abiWord s 16).setWidth 32).toNat (abiWord s 96).toNat
+
+def abiInputs (s : State) : List Region :=
+ [⟨s.gpr .rsi, (s.gpr .rdx).toNat⟩, ⟨s.gpr .rcx, (s.gpr .r8).toNat⟩,
+ ⟨abiWord s 32, (abiWord s 40).toNat⟩, ⟨abiWord s 48, (abiWord s 56).toNat⟩]
+def abiMatrix (s : State) : Region := ⟨abiWord s 64, (abiWord s 72).toNat * 1024⟩
+def abiWork (s : State) : Region := ⟨abiWord s 80, 16384⟩
+def abiOutput (s : State) : Region := ⟨abiWord s 88, (abiWord s 96).toNat⟩
+def abiArguments (s : State) : Region := ⟨s.gpr .rsp + BitVec.ofNat 64 8, 96⟩
+def abiBuffers (s : State) : List (Region × Bool) :=
+ (abiInputs s).map (·, false) ++ [(abiMatrix s, true), (abiWork s, true), (abiOutput s, true)]
+
+structure AbiEnvironment (s : State) : Prop where
+ stack : 344 ≤ (s.gpr .rsp).toNat
+ wrap : (s.gpr .rsp).toNat + 104 ≤ 2 ^ 64
+ rd : s.rd = abiInputs s ++ [abiArguments s]
+ wr : s.wr = [abiMatrix s, abiWork s, abiOutput s]
+ pairs : (abiBuffers s ++ [(abiArguments s, false)]).Pairwise
+ (fun a b => (a.2 || b.2) → a.1.Disjoint b.1)
+ reserved : ∀ r ∈ [⟨s.gpr .rsp, 8⟩, below (s.gpr .rsp) 344],
+ ∀ b ∈ abiBuffers s ++ [(abiArguments s, false)], r.Disjoint b.1
+ bounds : ∀ b ∈ abiBuffers s, b.1.base.toNat + b.1.len ≤ 2 ^ 64
+ kind : ((s.gpr .rdi).setWidth 32).toNat ≤ 2
+ valid : Spec.Argon2.valid (abiParams s) (s.gpr .rdx).toNat (s.gpr .r8).toNat
+ (abiWord s 40).toNat (abiWord s 56).toNat
+ threads : 1 ≤ ((abiWord s 24).setWidth 32).toNat ∧ ((abiWord s 24).setWidth 32).toNat < 2 ^ 24
+ blocks : (abiWord s 72).toNat = (abiParams s).blocks
+
+theorem abi_environment (s : State) (h : (Spec.Argon2.deriveContract X86_64.abi 344).pre s) :
+ AbiEnvironment s := by
+ sig_pre [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop] at h
+ sig_split h
+ constructor
+ all_goals sig_eval [abiInputs, abiArguments, abiMatrix, abiWork, abiOutput, abiBuffers,
+ abiWord, abiParams, below]
+ all_goals sig_and_intros
+ all_goals sig_close
+ all_goals with_reducible assumption
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAllocations.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAllocations.lean
new file mode 100644
index 000000000..ad747fe2d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveAllocations.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveHashInputs
+
+/-! The exact matrix and output allocations of the signature remain writable. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_matrix_region {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ (⟨FillKernel.matrix t, (abiParams s).blocks * 1024⟩ : Region) = abiMatrix s := by
+ have words := private_words h prepared
+ change (⟨Initial.wordAt t 232, (abiParams s).blocks * 1024⟩ : Region) = abiMatrix s
+ rw [words.matrix, ← h.blocks]; rfl
+
+theorem private_local_cover {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (n : Nat) (bound : n ≤ 272) : Covers [⟨t.gpr .rbp, n⟩] t.wr := by
+ intro p k ⟨region, hr, hc⟩
+ simp only [List.mem_singleton] at hr; subst region
+ refine ⟨⟨t.gpr .rbp, 272⟩, ?_, ?_⟩
+ · rw [prepared.wr, prepared.bp]
+ exact frameStart_locals s _
+ · unfold Region.Contains at hc ⊢
+ exact Nat.le_trans hc bound
+
+theorem private_matrix_cover {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ Covers [⟨FillKernel.matrix t, (abiParams s).blocks * 1024⟩] t.wr := by
+ have words := private_words h prepared
+ have member : abiMatrix s ∈ t.wr := private_wr_member prepared _ (by rw [h.wr]; exact List.mem_cons_self ..)
+ have matrix : (⟨FillKernel.matrix t, (abiParams s).blocks * 1024⟩ : Region) = abiMatrix s := by
+ change (⟨Initial.wordAt t 232, (abiParams s).blocks * 1024⟩ : Region) = abiMatrix s
+ rw [words.matrix, ← h.blocks]; rfl
+ intro p n ⟨region, hr, hc⟩
+ simp only [List.mem_singleton] at hr; subst region
+ exact ⟨abiMatrix s, member, matrix ▸ hc⟩
+
+theorem private_output_cover {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ Covers [⟨FinalOutput.output t, (abiParams s).tagLen⟩] t.wr := by
+ have words := private_words h prepared
+ have member : abiOutput s ∈ t.wr := private_wr_member prepared _ (by
+ rw [h.wr]; exact List.mem_cons_of_mem _ (List.mem_cons_of_mem _ (List.mem_singleton_self _)))
+ have output : (⟨FinalOutput.output t, (abiParams s).tagLen⟩ : Region) = abiOutput s := by
+ change (⟨Initial.wordAt t 256, (abiParams s).tagLen⟩ : Region) = abiOutput s
+ rw [words.output]; rfl
+ intro p n ⟨region, hr, hc⟩
+ simp only [List.mem_singleton] at hr; subst region
+ exact ⟨abiOutput s, member, output ▸ hc⟩
+
+theorem private_work_cover {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (n : Nat) (bound : n ≤ 16384) :
+ Covers [⟨FinalOutput.work t, n⟩] t.wr := by
+ have words := private_words h prepared
+ intro p k ⟨region, hr, hc⟩
+ simp only [List.mem_singleton] at hr; subst region
+ refine ⟨abiWork s, private_work_member h prepared, ?_⟩
+ change Region.Contains ⟨abiWord s 80, 16384⟩ p k
+ have pointer : FinalOutput.work t = abiWord s 80 := words.work
+ rw [pointer] at hc
+ unfold Region.Contains at hc ⊢
+ exact Nat.le_trans hc bound
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyCorrect.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyCorrect.lean
new file mode 100644
index 000000000..d98716586
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyCorrect.lean
@@ -0,0 +1,56 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveBodyPost
+
+/-! Preparation and the entire algorithm establish the shared API postcondition. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def bodyWrites (s : State) : List Region :=
+ [abiMatrix s, abiWork s, abiOutput s, ⟨(prologueState s).gpr .rsp, 272⟩,
+ below ((prologueState s).gpr .rsp) 24]
+
+structure BodyDone (s t : State) : Prop where
+ post : (Spec.Argon2.deriveContract X86_64.abi 344).post s t
+ sp : t.gpr .rsp = (prologueState s).gpr .rsp
+ rd : t.rd = (prologueState s).rd
+ wr : t.wr = (prologueState s).wr
+ frame : Frame (bodyWrites s) (prologueState s).mem t.mem
+
+theorem private_body_frame {s t u : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (done : InitialBody.Done t u (abiParams s)) :
+ Frame (bodyWrites s) (prologueState s).mem u.mem := by
+ have words := private_words h prepared
+ have matrix := private_matrix_region h prepared
+ have work : (⟨FinalOutput.work t, 16384⟩ : Region) = abiWork s := by
+ change (⟨Initial.wordAt t 248, 16384⟩ : Region) = abiWork s
+ rw [words.work]; rfl
+ have output : (⟨FinalOutput.output t, (abiParams s).tagLen⟩ : Region) = abiOutput s := by
+ change (⟨Initial.wordAt t 256, (abiParams s).tagLen⟩ : Region) = abiOutput s
+ rw [words.output]; rfl
+ have body := done.frame
+ rw [InitFill.writes, matrix, work, output, prepared.sp, prepared.bp] at body
+ apply ((private_prepare_frame prepared).mono ?_).trans (body.sub ?_)
+ · intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ simp only [bodyWrites, List.mem_cons, true_or, or_true]
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact ⟨abiMatrix s, by simp [bodyWrites], fun _ h => h⟩
+ · exact ⟨abiWork s, by simp [bodyWrites], fun _ h => h⟩
+ · exact ⟨abiOutput s, by simp [bodyWrites], fun _ h => h⟩
+ · exact ⟨below ((prologueState s).gpr .rsp) 24, by simp [bodyWrites], fun _ h => h⟩
+ · exact ⟨⟨(prologueState s).gpr .rsp, 272⟩, by simp [bodyWrites], Region.sub_prefix (by decide)⟩
+
+theorem body_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (h : AbiEnvironment s) :
+ WP isa (Impl.Argon2.X86_64.Derive.body name (HPrime.hash v)) (prologueState s) (BodyDone s) := by
+ unfold Impl.Argon2.X86_64.Derive.body
+ refine WP.seq ((prologue_prepare s h).mono ?_)
+ intro t prepared
+ refine (private_pipeline_ok v name s t h prepared).mono ?_
+ intro u done
+ exact ⟨private_done_post h prepared done, done.sp.trans prepared.sp,
+ done.rd.trans prepared.rd, done.wr.trans prepared.wr, private_body_frame h prepared done⟩
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyPost.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyPost.lean
new file mode 100644
index 000000000..0ca1f1968
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyPost.lean
@@ -0,0 +1,38 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveInputBytes
+
+/-! The complete body's digest is the public API postcondition on original input memory. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_done_post {s t u : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (done : InitialBody.Done t u (abiParams s)) :
+ (Spec.Argon2.deriveContract X86_64.abi 344).post s u := by
+ have words := private_words h prepared
+ have password := private_input_bytes h prepared (104, 96) (by decide)
+ have salt := private_input_bytes h prepared (88, 80) (by decide)
+ have secret := private_input_bytes h prepared (200, 208) (by decide)
+ have ad := private_input_bytes h prepared (216, 224) (by decide)
+ change Initial.inputBytes t 104 96 =
+ Spec.Blake2.bytesAt s.mem (Initial.wordAt t 104) (Initial.wordAt t 96).toNat at password
+ change Initial.inputBytes t 88 80 =
+ Spec.Blake2.bytesAt s.mem (Initial.wordAt t 88) (Initial.wordAt t 80).toNat at salt
+ change Initial.inputBytes t 200 208 =
+ Spec.Blake2.bytesAt s.mem (Initial.wordAt t 200) (Initial.wordAt t 208).toNat at secret
+ change Initial.inputBytes t 216 224 =
+ Spec.Blake2.bytesAt s.mem (Initial.wordAt t 216) (Initial.wordAt t 224).toNat at ad
+ rw [words.password, words.passwordLength] at password
+ rw [words.salt, words.saltLength] at salt
+ rw [words.secret, words.secretLength] at secret
+ rw [words.ad, words.adLength] at ad
+ have digest := done.digest
+ change Spec.Blake2.bytesAt u.mem (Initial.wordAt t 256) (abiParams s).tagLen =
+ Spec.Argon2.derive (abiParams s) (Initial.inputBytes t 104 96)
+ (Initial.inputBytes t 88 80) (Initial.inputBytes t 200 208) (Initial.inputBytes t 216 224) at digest
+ rw [words.output, password, salt, secret, ad] at digest
+ sig_post [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop]
+ exact digest
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyReady.lean
new file mode 100644
index 000000000..a064fd1ac
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodyReady.lean
@@ -0,0 +1,51 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFinalLayout
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveHeader
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveParameters
+
+/-! The shared API contract supplies the complete body's precondition after preparation. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem DeriveWords.of_state {s a b : State} (h : DeriveWords s a) (k : InitialBody.SameFrame a b) :
+ DeriveWords s b :=
+ ⟨(k.word 72).trans h.passes, (k.word 80).trans h.saltLength, (k.word 88).trans h.salt,
+ (k.word 96).trans h.passwordLength, (k.word 104).trans h.password, (k.word 112).trans h.kind,
+ (k.word 176).trans h.memory, (k.word 184).trans h.lanes, (k.word 200).trans h.secret,
+ (k.word 208).trans h.secretLength, (k.word 216).trans h.ad, (k.word 224).trans h.adLength,
+ (k.word 232).trans h.matrix, (k.word 240).trans h.blocks, (k.word 248).trans h.work,
+ (k.word 256).trans h.output, (k.word 264).trans h.tagLength⟩
+
+theorem private_body_ready {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ InitialBody.Ready (abiParams s) (dimensionState t (abiParams s)) := by
+ let a := dimensionState t (abiParams s)
+ have keeps : InitialBody.SameFrame t a := dimension_frame t (abiParams s)
+ have words : DeriveWords s a := (private_words h prepared).of_state keeps
+ have matrix : FillKernel.matrix a = FillKernel.matrix t := by
+ unfold FillKernel.matrix; rw [keeps.mem, keeps.bp]
+ have scratch : a.gpr .rbx = FinalOutput.work a := by
+ rw [keeps.bx, private_scratch h prepared]
+ exact words.work.symm
+ refine ⟨keeps.hashSpace (private_hash_space h prepared),
+ fun input hi => keeps.input (private_hash_inputs h prepared input hi), private_header words, ?_⟩
+ refine ⟨?_, (private_fill_environment h prepared).of_state keeps.bp keeps.sp keeps.mem keeps.rd keeps.wr,
+ keeps.output (private_final_layout h prepared), h.valid.2.2.1, scratch⟩
+ refine ⟨?_, ?_, ?_, ?_, ?_, words.lanes, ?_, ?_⟩
+ · rw [matrix]
+ exact (private_memory_space h prepared).same keeps.wr keeps.bp keeps.bx keeps.sp
+ · rw [keeps.rd, keeps.wr, keeps.bp]; exact private_local_read prepared 232 8 (by decide)
+ · rw [keeps.rd, keeps.wr, keeps.bp]; exact private_local_read prepared 184 8 (by decide)
+ · rw [keeps.rd, keeps.wr, keeps.bp]; exact private_local_read prepared 240 8 (by decide)
+ · rfl
+ · rw [← Proof.Argon2.blocks_lanes (abiParams s) h.valid.1]; exact words.blocks
+ · exact RegUpd.gpr_setReg_self ..
+
+theorem private_pipeline_ok (v : Proof.Blake2.X86_64.Backend) (name : String)
+ (s t : State) (h : AbiEnvironment s) (prepared : PrivatePrepared (prologueState s) t) :
+ WP isa (.seq Impl.Argon2.X86_64.Parameters.code
+ (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v))) t (InitialBody.Done t · (abiParams s)) :=
+ parameters_body_ok v name t (abiParams s) (private_parameters h prepared) (private_body_ready h prepared)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodySaved.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodySaved.lean
new file mode 100644
index 000000000..a56afa660
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveBodySaved.lean
@@ -0,0 +1,50 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveBodyCorrect
+
+/-! The whole body leaves the prologue's six saved-register slots untouched. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def savedSlot (s : State) (j : Nat) : Region :=
+ ⟨s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1)), 8⟩
+
+theorem saved_slot_sub (s : State) (j : Nat) (bound : j < 6) :
+ Region.Sub (savedSlot s j) (below (s.gpr .rsp) 344) :=
+ Offset.sub_below _ (by omega) (by omega)
+
+theorem saved_slot_address (s : State) (j : Nat) (bound : j < 6) :
+ (savedSlot s j).base = (prologueState s).gpr .rsp + BitVec.ofNat 64 (320 - 8 * (j + 1)) := by
+ rw [prologue_sp]
+ exact Offset.sub_ofNat_eq _ (by omega)
+
+theorem saved_slot_buffers {s : State} (h : AbiEnvironment s) (j : Nat) (bound : j < 6)
+ (buffer : Region × Bool) (member : buffer ∈ abiBuffers s ++ [(abiArguments s, false)]) :
+ (savedSlot s j).Disjoint buffer.1 :=
+ (h.reserved _ (List.mem_cons_of_mem _ (List.mem_singleton_self _)) buffer member).sub_left
+ (saved_slot_sub s j bound)
+
+theorem saved_slot_writes {s : State} (h : AbiEnvironment s) (j : Nat) (bound : j < 6) :
+ ∀ r ∈ bodyWrites s, (savedSlot s j).Disjoint r := by
+ intro r hr
+ simp only [bodyWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · apply saved_slot_buffers h j bound (abiMatrix s, true)
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ · apply saved_slot_buffers h j bound (abiWork s, true)
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ · apply saved_slot_buffers h j bound (abiOutput s, true)
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ · change (⟨(savedSlot s j).base, 8⟩ : Region).Disjoint _
+ rw [saved_slot_address s j bound]
+ exact Offset.disjoint_base _ (by omega) (by omega)
+ · change (⟨(savedSlot s j).base, 8⟩ : Region).Disjoint _
+ rw [saved_slot_address s j bound]
+ exact Offset.disjoint_below _ (by omega)
+
+theorem BodyDone.saved {s t : State} (h : AbiEnvironment s) (done : BodyDone s t) (j : Nat) (bound : j < 6) :
+ t.mem.readW (s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1))) 64 =
+ (prologueState s).mem.readW (s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1))) 64 :=
+ done.frame.readW (r := savedSlot s j) (Region.contains_self _ _) (saved_slot_writes h j bound) (by decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCT.lean
new file mode 100644
index 000000000..4bbd3ec7f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCT.lean
@@ -0,0 +1,43 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePrivatePublic
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePrepareCT
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFrameCT
+
+/-! The entire entry point leaks only the exact allowance of the shared contract. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def AbiRelated (s t : State) : Prop := AbiEnvironment s ∧ AbiEnvironment t ∧ AbiPublic s t
+
+def PrologueRelated (a b : State) : Prop := ∃ s t, AbiRelated s t ∧ a = prologueState s ∧ b = prologueState t
+
+theorem body_rel (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ RelCT isa PrologueRelated (Impl.Argon2.X86_64.Derive.body name (HPrime.hash v)) (fun _ _ => True) := by
+ have preparation := (prepare_rel.mono (P' := PrologueRelated) (by
+ rintro a b ⟨s, t, h, rfl, rfl⟩
+ rw [prologue_sp, prologue_sp, h.2.2.sp]) (fun _ _ h => h)).wpDep (F := fun a b =>
+ ∃ s, a = prologueState s ∧ AbiEnvironment s ∧ PrivatePrepared a b) (by
+ rintro a b ⟨s, t, h, rfl, rfl⟩
+ exact ⟨(prologue_prepare s h.1).mono (fun _ prepared => ⟨s, rfl, h.1, prepared⟩),
+ (prologue_prepare t h.2.1).mono (fun _ prepared => ⟨t, rfl, h.2.1, prepared⟩)⟩)
+ unfold Impl.Argon2.X86_64.Derive.body
+ apply preparation.seq
+ apply (RelCT.exists_ (fun p => parameters_body_rel v name p)).mono ?_ (fun _ _ h => h)
+ rintro a b ⟨_, x, y, ⟨s, t, h, rfl, rfl⟩,
+ ⟨u, hu, _, prepared₁⟩, ⟨w, hw, _, prepared₂⟩⟩
+ have left : PrivatePrepared (prologueState s) a := prepared₁
+ have right : PrivatePrepared (prologueState t) b := prepared₂
+ exact ⟨abiParams s, private_parameters_related h.1 h.2.1 h.2.2 left right⟩
+
+theorem code_ct (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ ConstantTime isa (Spec.Argon2.deriveContract X86_64.abi 344).pre
+ (Spec.Argon2.deriveContract X86_64.abi 344).pub
+ (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v)) := by
+ have full := frame_rel Impl.Argon2.X86_64.Derive.saved _ AbiRelated
+ (fun _ _ h => h.2.2.sp) (body_rel v name)
+ exact (full.mono (fun s t h =>
+ ⟨abi_environment s h.1, abi_environment t h.2.1, abi_public s t h.2.2⟩)
+ (fun _ _ h => h)).constantTime
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveContract.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveContract.lean
new file mode 100644
index 000000000..5c53449c2
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveContract.lean
@@ -0,0 +1,35 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveAbi
+
+/-! A concrete caller establishes satisfiability of the shared derivation contract. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def satArgs : List Nat := [8, 1, 1, 0, 0, 0, 0, 0x10000, 8, 0x20000, 0x30000, 4]
+
+def satMem (a : Addr) : Byte :=
+ let d := a.toNat - 0x40008
+ if 0x40008 ≤ a.toNat ∧ a.toNat < 0x40068 then
+ ((BitVec.ofNat 64 (satArgs[d / 8]?.getD 0)) >>> (8 * (d % 8))).setWidth 8
+ else 0
+
+def satState : State where
+ gpr r := match r with
+ | .rsp => 0x40000 | .r9 => 1 | _ => 0
+ cf := none
+ zf := none
+ sf := none
+ of := none
+ mem := satMem
+ rd := [⟨0, 0⟩, ⟨0, 0⟩, ⟨0, 0⟩, ⟨0, 0⟩, ⟨0x40008, 96⟩]
+ wr := [⟨0x10000, 8192⟩, ⟨0x20000, 16384⟩, ⟨0x30000, 4⟩]
+
+theorem contract_sat : ∃ s, (Spec.Argon2.deriveContract X86_64.abi 344).pre s := by
+ refine ⟨satState, ?_⟩
+ sig_sat_check [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop,
+ satState, satMem, satArgs, Spec.Argon2.params, Spec.Argon2.valid,
+ Spec.Argon2.Params.blocks, Spec.Argon2.Params.segmentLen]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArg.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArg.lean
new file mode 100644
index 000000000..66536c307
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArg.lean
@@ -0,0 +1,46 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveNormalize
+
+/-! Copy a read-only caller argument into the private derivation frame. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def copySource (j : Nat) : Nat := 328 + 8 * j
+def copyDestination (j : Nat) : Nat := 176 + 8 * j
+
+structure CopiedArg (s t : State) (j : Nat) : Prop where
+ mem : t.mem = s.mem.writeW (s.gpr .rsp + BitVec.ofNat 64 (copyDestination j))
+ (s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 64)
+ regs : ∀ r, r ≠ .rax → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem copyArg_ok (s : State) (j : Nat)
+ (read : InRegions (s.rd ++ s.wr) (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 8)
+ (write : InRegions s.wr (s.gpr .rsp + BitVec.ofNat 64 (copyDestination j)) 8) :
+ WP isa (.block (Impl.Argon2.X86_64.Derive.copyArg j)) s (CopiedArg s · j) := by
+ change InRegions (s.rd ++ s.wr) (s.gpr .rsp + BitVec.ofNat 64 (328 + 8 * j)) 8 at read
+ change InRegions s.wr (s.gpr .rsp + BitVec.ofNat 64 (176 + 8 * j)) 8 at write
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.Derive.copyArg, runBlock_cons, runStep_some, runBlock_nil,
+ exec, State.load64, State.store64, readSrc, State.ea, Impl.Argon2.X86_64.at_,
+ BitVec.ofInt_natCast, RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg,
+ RegUpd.wr_setReg, read, write, reduceCtorEq,
+ ite_true, ite_false, Option.some.injEq, Option.map_some, exists_eq_left']
+ refine ⟨rfl, ?_, rfl, rfl, rfl⟩
+ intro r hr
+ exact RegUpd.gpr_setReg_of_ne _ _ hr
+
+theorem CopiedArg.frame {s t : State} {j : Nat} (h : CopiedArg s t j) :
+ Frame [⟨s.gpr .rsp + BitVec.ofNat 64 (copyDestination j), 8⟩] s.mem t.mem := by
+ rw [h.mem]
+ exact (Frame.refl _ _).writeW (List.mem_singleton_self _) _ (Region.contains_self _ _)
+
+theorem CopiedArg.word {s t : State} {j : Nat} (h : CopiedArg s t j) :
+ t.mem.readW (t.gpr .rsp + BitVec.ofNat 64 (copyDestination j)) 64 =
+ s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 64 := by
+ rw [h.regs .rsp (by decide), h.mem, Mem.readW_writeW_self64]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArgs.lean
new file mode 100644
index 000000000..23b570dff
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCopyArgs.lean
@@ -0,0 +1,70 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveCopyArg
+
+/-! Copy all stack arguments without modifying their caller-owned storage. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure CopiedArgs (s t : State) (js : List Nat) : Prop where
+ values : ∀ j ∈ js, t.mem.readW (t.gpr .rsp + BitVec.ofNat 64 (copyDestination j)) 64 =
+ s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 64
+ regs : ∀ r, r ≠ .rax → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ frame : Frame (js.map fun j => (⟨s.gpr .rsp + BitVec.ofNat 64 (copyDestination j), 8⟩ : Region)) s.mem t.mem
+
+theorem CopiedArgs.other_word {s t : State} {js : List Nat} (h : CopiedArgs s t js)
+ (d : Nat) (bound : d + 8 ≤ 2 ^ 64)
+ (separate : ∀ j ∈ js, d + 8 ≤ copyDestination j ∨ copyDestination j + 8 ≤ d)
+ (bounds : ∀ j ∈ js, copyDestination j + 8 ≤ 2 ^ 64) :
+ t.mem.readW (t.gpr .rsp + BitVec.ofNat 64 d) 64 = s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 d) 64 := by
+ rw [h.regs .rsp (by decide)]
+ apply h.frame.readW (r := ⟨s.gpr .rsp + BitVec.ofNat 64 d, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro region hr
+ obtain ⟨j, hj, rfl⟩ := List.mem_map.mp hr
+ exact Offset.disjoint _ (separate j hj) bound (bounds j hj)
+
+theorem copyArgs_ok (js : List Nat) (s : State) (bounds : ∀ j ∈ js, j < 12)
+ (distinct : js.Nodup)
+ (read : ∀ j ∈ js, InRegions (s.rd ++ s.wr) (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 8)
+ (write : ∀ j ∈ js, InRegions s.wr (s.gpr .rsp + BitVec.ofNat 64 (copyDestination j)) 8) :
+ WP isa (.block (js.flatMap Impl.Argon2.X86_64.Derive.copyArg)) s (CopiedArgs s · js) := by
+ induction js generalizing s with
+ | nil => exact WP.block_nil ⟨by simp, fun _ _ => rfl, rfl, rfl, rfl, Frame.refl _ _⟩
+ | cons j js ih =>
+ have nodup := List.nodup_cons.mp distinct
+ have jBound := bounds j (List.mem_cons_self ..)
+ have tailBounds : ∀ x ∈ js, x < 12 := fun x hx => bounds x (List.mem_cons_of_mem _ hx)
+ rw [List.flatMap_cons, WP.block_append_iff]
+ refine (copyArg_ok s j (read j (List.mem_cons_self ..)) (write j (List.mem_cons_self ..))).mono ?_
+ intro t ht
+ refine (ih t tailBounds nodup.2
+ (fun x hx => by rw [ht.rd, ht.wr, ht.regs .rsp (by decide)]; exact read x (List.mem_cons_of_mem _ hx))
+ (fun x hx => by rw [ht.wr, ht.regs .rsp (by decide)]; exact write x (List.mem_cons_of_mem _ hx))).mono ?_
+ intro u hu
+ refine ⟨?_, fun r hr => (hu.regs r hr).trans (ht.regs r hr),
+ hu.rd.trans ht.rd, hu.wr.trans ht.wr, hu.mxcsr.trans ht.mxcsr, ?_⟩
+ · intro x hx
+ rcases List.mem_cons.mp hx with rfl | hx
+ · rw [hu.other_word (copyDestination x) (by unfold copyDestination; omega) (by
+ intro y hy
+ have ne : y ≠ x := fun eq => nodup.1 (eq ▸ hy)
+ unfold copyDestination; omega) (by
+ intro y hy; have := tailBounds y hy; unfold copyDestination; omega)]
+ exact ht.word
+ · rw [hu.values x hx, ht.regs .rsp (by decide), ht.mem]
+ apply Mem.readW_writeW_sep ?_ (by decide)
+ have xBound := tailBounds x hx
+ exact Offset.sep _ (Or.inr (by unfold copyDestination copySource; omega))
+ (by unfold copySource; omega) (by unfold copyDestination; omega)
+ · apply (ht.frame.mono ?_).trans
+ · have frame := hu.frame
+ rw [ht.regs .rsp (by decide)] at frame
+ exact frame.mono (fun _ h => List.mem_cons_of_mem _ h)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ exact List.mem_cons_self ..
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCorrect.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCorrect.lean
new file mode 100644
index 000000000..e0106211c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveCorrect.lean
@@ -0,0 +1,29 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveReturn
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveMxcsr
+
+/-! Functional correctness, termination, memory safety, and the complete System V ABI. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem code_correct (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State)
+ (pre : (Spec.Argon2.deriveContract X86_64.abi 344).pre s) :
+ ∃ tr t, Exec isa (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v)) s tr t ∧
+ abiPreserved s t ∧ (Spec.Argon2.deriveContract X86_64.abi 344).post s t := by
+ obtain ⟨tr, t, run, post, regs, frame⟩ := code_wp v name s pre
+ refine ⟨tr, t, run, abiPreserved_of_exec (code_mxcsr v name) run ⟨regs, ?_⟩, post⟩
+ have h := abi_environment s pre
+ apply frame.readW (r := ⟨s.gpr .rsp, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro r hr
+ simp only [wholeWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact h.reserved _ (by simp) (abiMatrix s, true)
+ (List.mem_append_left _ (List.mem_append_right _ (by simp)))
+ · exact h.reserved _ (by simp) (abiWork s, true)
+ (List.mem_append_left _ (List.mem_append_right _ (by simp)))
+ · exact h.reserved _ (by simp) (abiOutput s, true)
+ (List.mem_append_left _ (List.mem_append_right _ (by simp)))
+ · exact Offset.base_disjoint_below _ (by decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveEntry.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveEntry.lean
new file mode 100644
index 000000000..8b5eb5518
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveEntry.lean
@@ -0,0 +1,29 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveStore
+
+/-! Establish the local frame base and normalize register-passed u32 arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure Entered (s t : State) : Prop where
+ bp : t.gpr .rbp = s.gpr .rsp
+ kind : t.gpr .rdi = ((s.gpr .rdi).setWidth 32).setWidth 64
+ passes : t.gpr .r9 = ((s.gpr .r9).setWidth 32).setWidth 64
+ regs : ∀ r, r ≠ .rbp → r ≠ .rdi → r ≠ .r9 → t.gpr r = s.gpr r
+ mem : t.mem = s.mem
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem entry_ok (s : State) :
+ WP isa (.block [.mov .rbp (.reg .rsp), .mov32 .rdi (.reg .rdi), .mov32 .r9 (.reg .r9)]) s (Entered s) := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, readSrc32,
+ State.setReg32, RegUpd.gpr_setReg, reduceCtorEq,
+ ite_false, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨rfl, rfl, rfl, ?_, rfl, rfl, rfl, rfl⟩
+ intro r hb hd h9
+ simp only [RegUpd.gpr_setReg, hb, hd, h9, ite_false]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFillLayout.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFillLayout.lean
new file mode 100644
index 000000000..4fad62aad
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFillLayout.lean
@@ -0,0 +1,62 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveMemorySpace
+
+/-! One reviewed allocation supplies all filling and address-generation ranges. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_fill_layout {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : FillKernel.Layout (abiParams s) t := by
+ have space := private_memory_space h prepared
+ rw [private_matrix_bytes h] at space
+ refine ⟨?_, private_local_write prepared 16 8 (by decide), space.matrix,
+ private_work_cover h prepared 5120 (by decide), ?_, space.frameMatrix.symm, ?_, ?_,
+ private_frame_stack prepared 8 (by decide), ?_⟩
+ · intro d hd
+ have bounds : ∀ d ∈ [0, 16, 184, 232, 248], d + 8 ≤ 272 := by decide
+ exact private_local_read prepared d 8 (bounds d hd)
+ · have pointer : t.gpr .rbx = FillKernel.work t := by
+ rw [private_scratch h prepared]; exact (private_words h prepared).work.symm
+ rw [← pointer]
+ exact space.matrixWork.sub_right (Region.sub_prefix (by decide))
+ · exact (space.stackMatrix.sub_left (below_sub (by decide) (by decide))).symm
+ · have pointer : t.gpr .rbx = FillKernel.work t := by
+ rw [private_scratch h prepared]; exact (private_words h prepared).work.symm
+ rw [← pointer]
+ exact space.frameWork.sub_right (Region.sub_prefix (by decide))
+ · have pointer : t.gpr .rbx = FillKernel.work t := by
+ rw [private_scratch h prepared]; exact (private_words h prepared).work.symm
+ rw [← pointer]
+ exact (space.stackWork.sub_left (below_sub (by decide) (by decide))).sub_right
+ (Region.sub_prefix (by decide))
+
+theorem private_address_layout {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : AddressCalls.Ready t := by
+ have space := private_memory_space h prepared
+ have pointer : t.gpr .rbx = AddressCalls.work t := by
+ rw [private_scratch h prepared]; exact (private_words h prepared).work.symm
+ refine ⟨private_local_read prepared 248 8 (by decide), private_work_cover h prepared 8192 (by decide),
+ ?_, private_frame_stack prepared 8 (by decide), ?_⟩
+ · rw [← pointer]; exact space.frameWork.sub_right (Region.sub_prefix (by decide))
+ · rw [← pointer]
+ exact (space.stackWork.sub_left (below_sub (by decide) (by decide))).sub_right (Region.sub_prefix (by decide))
+
+theorem private_fill_environment {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : FillSetup.Environment (abiParams s) t := by
+ have space := private_memory_space h prepared
+ have words := private_words h prepared
+ have pointer : t.gpr .rbx = AddressCalls.work t := by
+ rw [private_scratch h prepared]; exact words.work.symm
+ rw [private_matrix_bytes h] at space
+ refine ⟨?_, h.valid.2.2.2.1, private_fill_layout h prepared, private_address_layout h prepared, ?_,
+ private_local_write prepared 8 8 (by decide), private_local_write prepared 0 8 (by decide),
+ ?_, words.blocks, words.passes, words.kind, words.lanes⟩
+ · refine ⟨h.valid.1, Nat.lt_trans h.valid.2.1 (by decide), h.valid.2.2.2.2.1,
+ h.valid.2.2.2.2.2.1, by decide, h.valid.1, by decide⟩
+ · intro d hd
+ have bounds : ∀ d ∈ [0, 8, 72, 112, 240], d + 8 ≤ 272 := by decide
+ exact private_local_read prepared d 8 (bounds d hd)
+ · rw [← pointer]; exact space.matrixWork.sub_right (Region.sub_prefix (by decide))
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFinalLayout.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFinalLayout.lean
new file mode 100644
index 000000000..e5164b76c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFinalLayout.lean
@@ -0,0 +1,56 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFillLayout
+
+/-! Final lane reduction and H′ use the matrix and disjoint output/scratch allocations. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_final_layout {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : FinalOutput.Ready (abiParams s) t := by
+ have words := private_words h prepared
+ have separation := abi_separation h
+ have environment := private_fill_environment h prepared
+ have parameters := environment.parameters
+ have blocks := Proof.Argon2.lastIndex_bounds (abiParams s) parameters.lanesPositive
+ parameters.segment_bound.1 0 parameters.lanesPositive
+ have minimum : 1024 ≤ (abiParams s).blocks * 1024 := by
+ have positive : 1 ≤ (abiParams s).blocks := by omega
+ simpa only [Nat.one_mul] using Nat.mul_le_mul_right 1024 positive
+ have matrix := private_matrix_region h prepared
+ have work : FinalOutput.work t = (abiWork s).base := words.work
+ have output : (⟨FinalOutput.output t, (abiParams s).tagLen⟩ : Region) = abiOutput s := by
+ change (⟨Initial.wordAt t 256, (abiParams s).tagLen⟩ : Region) = abiOutput s
+ rw [words.output]; rfl
+ have matrixMember : (abiMatrix s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ have workMember : (abiWork s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ have outputMember : (abiOutput s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ have matrixWork : (⟨ReductionState.matrix t, 1024⟩ : Region).Disjoint ⟨FinalOutput.work t, 16384⟩ := by
+ rw [work]
+ apply Region.Disjoint.sub_left separation.matrixWork
+ rw [← matrix]
+ exact Region.sub_prefix minimum
+ have stackMatrix : (below (t.gpr .rsp) 24).Disjoint ⟨ReductionState.matrix t, 1024⟩ := by
+ apply Region.Disjoint.sub_right
+ (private_stack_disjoint h prepared (abiMatrix s, true) matrixMember 24 (by decide))
+ rw [← matrix]; exact Region.sub_prefix minimum
+ refine ⟨by have tag := h.valid.2.2.2.2.2.2.1; omega, h.valid.2.2.2.2.2.2.2.1,
+ ?_, words.tagLength, ?_, private_output_cover h prepared, ?_, matrixWork, ?_, stackMatrix, ?_, ?_⟩
+ · intro d hd
+ have bounds : ∀ d ∈ [232, 256, 264, 248], d + 8 ≤ 272 := by decide
+ exact private_local_read prepared d 8 (bounds d hd)
+ · intro p n ⟨region, member, contains⟩
+ simp only [List.mem_singleton] at member; subst region
+ have contained : Region.Contains ⟨FillKernel.matrix t, (abiParams s).blocks * 1024⟩ p n := by
+ unfold Region.Contains at contains ⊢; exact Nat.le_trans contains minimum
+ obtain ⟨r, hr, hc⟩ := private_matrix_cover h prepared p n ⟨_, List.mem_singleton_self _, contained⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ · rw [work]; exact private_work_member h prepared
+ · rw [output, work]; exact separation.outputWork
+ · rw [output]; exact private_stack_disjoint h prepared (abiOutput s, true) outputMember 24 (by decide)
+ · rw [work]; exact private_stack_disjoint h prepared (abiWork s, true) workMember 24 (by decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrame.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrame.lean
new file mode 100644
index 000000000..07acd20b3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrame.lean
@@ -0,0 +1,60 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Derive
+import VerifiedGarbage.Proof.Framework.X86_64.Frame
+
+/-! Compose the nested saved-register frames and the 272-byte local allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def frameStart (s : State) : List Reg → State
+ | [] => pushed (List.replicate 34 .rax) s
+ | r :: rs => frameStart (pushed [r] s) rs
+
+def frameEnd (s : State) : List Reg → State
+ | [] => popped .rax 34 s
+ | r :: rs => popped r 1 (frameEnd s rs)
+
+theorem frameEnd_metadata (s t : State) (rs : List Reg)
+ (sp : t.gpr .rsp = (frameStart s rs).gpr .rsp)
+ (wr : t.wr = (frameStart s rs).wr) :
+ (frameEnd t rs).gpr .rsp = s.gpr .rsp ∧ (frameEnd t rs).wr = s.wr := by
+ induction rs generalizing s with
+ | nil =>
+ constructor
+ · rw [frameEnd, popped_rsp, sp, frameStart, pushed_rsp]
+ simp only [List.length_replicate, Nat.reduceMul, BitVec.sub_add_cancel]
+ · rw [frameEnd, popped_wr, wr, frameStart, pushed_wr]; rfl
+ | cons r rs ih =>
+ obtain ⟨innerSp, innerWr⟩ := ih (pushed [r] s) sp wr
+ constructor
+ · rw [frameEnd, popped_rsp, innerSp, pushed_rsp]
+ simp only [List.length_singleton, Nat.mul_one, BitVec.sub_add_cancel]
+ · rw [frameEnd, popped_wr, innerWr, pushed_wr]; rfl
+
+theorem frame_ok (s : State) (rs : List Reg) (body : Prog isa) (Q : State → Prop)
+ (notSp : .rsp ∉ rs) (space : 272 + 8 * rs.length ≤ (s.gpr .rsp).toNat)
+ (run : WP isa body (frameStart s rs) fun t =>
+ t.gpr .rsp = (frameStart s rs).gpr .rsp ∧ t.wr = (frameStart s rs).wr ∧ Q (frameEnd t rs)) :
+ WP isa (Impl.Argon2.X86_64.Derive.frame body rs) s Q := by
+ induction rs generalizing s Q with
+ | nil =>
+ exact WP.frame (by decide) (by decide) (by decide) (by simpa using space) run
+ | cons r rs ih =>
+ simp only [List.mem_cons, not_or] at notSp
+ have pushedBound : ((pushed [r] s).gpr .rsp).toNat = (s.gpr .rsp).toNat - 8 := by
+ rw [pushed_rsp]
+ simp only [List.length_singleton, Nat.mul_one]
+ exact toNat_sub_ofNat (by simp only [List.length_cons] at space; omega)
+ have innerSpace : 272 + 8 * rs.length ≤ ((pushed [r] s).gpr .rsp).toNat := by
+ rw [pushedBound]; simp only [List.length_cons] at space; omega
+ apply WP.frame (by simp) (by simpa using notSp.1) (Ne.symm notSp.1)
+ (by simp only [List.length_cons] at space; simp only [List.length_singleton, Nat.mul_one]; omega)
+ apply ih (pushed [r] s) (fun t => t.gpr .rsp = (pushed [r] s).gpr .rsp ∧
+ t.wr = (pushed [r] s).wr ∧ Q (popped r 1 t)) notSp.2 innerSpace
+ apply run.mono
+ rintro t ⟨sp, wr, result⟩
+ obtain ⟨endSp, endWr⟩ := frameEnd_metadata (pushed [r] s) t rs sp wr
+ exact ⟨sp, wr, endSp, endWr, result⟩
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameCT.lean
new file mode 100644
index 000000000..b3a931b99
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameCT.lean
@@ -0,0 +1,25 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFrame
+
+/-! Saving and restoring the ABI frame leaks only the public stack pointer. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem frame_rel (rs : List Reg) (body : Prog isa) (P : State → State → Prop)
+ (sp : ∀ s t, P s t → s.gpr .rsp = t.gpr .rsp)
+ (run : RelCT isa (fun a b => ∃ s t, P s t ∧ a = frameStart s rs ∧ b = frameStart t rs)
+ body (fun _ _ => True)) :
+ RelCT isa P (Impl.Argon2.X86_64.Derive.frame body rs) (fun _ _ => True) := by
+ induction rs generalizing P with
+ | nil => exact RelCT.frame sp run
+ | cons r rs ih =>
+ apply RelCT.frame sp
+ apply ih (fun a b => ∃ s t, P s t ∧ a = pushed [r] s ∧ b = pushed [r] t) ?_ ?_
+ · rintro a b ⟨s, t, hp, rfl, rfl⟩
+ rw [pushed_rsp, pushed_rsp, sp s t hp]
+ · apply run.mono ?_ (fun _ _ h => h)
+ rintro a b ⟨u, v, ⟨s, t, hp, rfl, rfl⟩, rfl, rfl⟩
+ exact ⟨s, t, hp, rfl, rfl⟩
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameState.lean
new file mode 100644
index 000000000..5785e5556
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveFrameState.lean
@@ -0,0 +1,73 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFrame
+import VerifiedGarbage.Proof.Framework.Offset
+
+/-! Exact stack depth and memory modified by the entry-point prologue. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem frameStart_sp (s : State) (rs : List Reg) :
+ (frameStart s rs).gpr .rsp = s.gpr .rsp - BitVec.ofNat 64 (272 + 8 * rs.length) := by
+ induction rs generalizing s with
+ | nil => rw [frameStart, pushed_rsp]; rfl
+ | cons r rs ih =>
+ rw [frameStart, ih, pushed_rsp]
+ simp only [List.length_cons, List.length_nil, Nat.zero_add, Nat.mul_one]
+ rw [BitVec.sub_sub, ← BitVec.ofNat_add]
+ exact congrArg (fun n => s.gpr .rsp - BitVec.ofNat 64 n) (by omega)
+
+theorem frameStart_reg (s : State) (rs : List Reg) (r : Reg) (notSp : r ≠ .rsp) :
+ (frameStart s rs).gpr r = s.gpr r := by
+ induction rs generalizing s with
+ | nil => exact pushed_gpr _ _ notSp
+ | cons x xs ih => exact (ih (pushed [x] s)).trans (pushed_gpr _ _ notSp)
+
+theorem frameStart_rd (s : State) (rs : List Reg) : (frameStart s rs).rd = s.rd := by
+ induction rs generalizing s with
+ | nil => exact pushed_rd ..
+ | cons r rs ih => exact (ih (pushed [r] s)).trans (pushed_rd ..)
+
+theorem frameStart_frame (s : State) (rs : List Reg) (notSp : .rsp ∉ rs)
+ (space : 272 + 8 * rs.length ≤ (s.gpr .rsp).toNat) :
+ Frame [below (s.gpr .rsp) (272 + 8 * rs.length)] s.mem (frameStart s rs).mem := by
+ induction rs generalizing s with
+ | nil =>
+ exact (pushRegs_mem s (List.replicate 34 .rax) (by decide) (by simpa using space)).1
+ | cons r rs ih =>
+ simp only [List.mem_cons, not_or] at notSp
+ have enough : 8 ≤ (s.gpr .rsp).toNat := by simp only [List.length_cons] at space; omega
+ have innerSpace : 272 + 8 * rs.length ≤ ((pushed [r] s).gpr .rsp).toNat := by
+ rw [pushed_rsp]
+ simp only [List.length_singleton, Nat.mul_one]
+ rw [toNat_sub_ofNat enough]
+ simp only [List.length_cons] at space; omega
+ have outer := (pushRegs_mem s [r] (by simpa using notSp.1)
+ (by simpa using enough)).1
+ have inner := ih (pushed [r] s) notSp.2 innerSpace
+ apply (outer.sub ?_).trans (inner.sub ?_)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ refine ⟨_, List.mem_singleton_self _, ?_⟩
+ exact Offset.sub_below (s.gpr .rsp) (by simp only [List.length_cons, List.length_nil]; omega)
+ (by simp only [List.length_singleton, Nat.mul_one]; omega)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ refine ⟨_, List.mem_singleton_self _, ?_⟩
+ rw [pushed_rsp]
+ simp only [List.length_cons, List.length_nil, Nat.zero_add, Nat.mul_one, below]
+ rw [BitVec.sub_sub, ← BitVec.ofNat_add,
+ show 8 + (272 + 8 * rs.length) = 272 + 8 * (rs.length + 1) by omega]
+ exact Region.sub_prefix (by omega)
+
+theorem frameEnd_mem (s : State) (rs : List Reg) : (frameEnd s rs).mem = s.mem := by
+ induction rs with
+ | nil => exact popped_mem ..
+ | cons r rs ih => exact (popped_mem ..).trans ih
+
+theorem frameEnd_rd (s : State) (rs : List Reg) : (frameEnd s rs).rd = s.rd := by
+ induction rs with
+ | nil => exact popped_rd ..
+ | cons r rs ih => exact (popped_rd ..).trans ih
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashInputs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashInputs.lean
new file mode 100644
index 000000000..e4f246b1d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashInputs.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveHashSpace
+
+/-! All four secret inputs keep their original pointers and lengths in the private frame. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+open VG.Proof.Argon2.X86_64.Initial (wordAt inputRegion)
+
+theorem private_input_member {s t : State} (words : DeriveWords s t) (input : Nat × Nat)
+ (member : input ∈ Initial.inputs) : inputRegion t input.1 input.2 ∈ abiInputs s := by
+ simp only [Initial.inputs, List.mem_cons, List.not_mem_nil, or_false] at member
+ rcases member with rfl | rfl | rfl | rfl
+ · change (⟨wordAt t 104, (wordAt t 96).toNat⟩ : Region) ∈ abiInputs s
+ rw [words.password, words.passwordLength]
+ exact List.mem_cons_self ..
+ · change (⟨wordAt t 88, (wordAt t 80).toNat⟩ : Region) ∈ abiInputs s
+ rw [words.salt, words.saltLength]
+ exact List.mem_cons_of_mem _ (List.mem_cons_self ..)
+ · change (⟨wordAt t 200, (wordAt t 208).toNat⟩ : Region) ∈ abiInputs s
+ rw [words.secret, words.secretLength]
+ exact List.mem_cons_of_mem _ (List.mem_cons_of_mem _ (List.mem_cons_self ..))
+ · change (⟨wordAt t 216, (wordAt t 224).toNat⟩ : Region) ∈ abiInputs s
+ rw [words.ad, words.adLength]
+ exact List.mem_cons_of_mem _ (List.mem_cons_of_mem _ (List.mem_cons_of_mem _ (List.mem_singleton_self _)))
+
+theorem private_hash_inputs {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ ∀ input ∈ Initial.inputs, Initial.InputReady t input.1 input.2 := by
+ have words := private_words h prepared
+ have space := private_hash_space h prepared
+ have separation := abi_separation h
+ have scratch := private_scratch h prepared
+ intro input hi
+ have region := private_input_member words input hi
+ have facts : ∀ input ∈ Initial.inputs, input.1 ∈ Initial.slots ∧ input.2 ∈ Initial.slots ∧
+ input.1 + 8 ≤ 272 ∧ input.2 + 8 ≤ 272 := by decide
+ obtain ⟨pointerSlot, lengthSlot, pointerBound, lengthBound⟩ := facts input hi
+ have buffer : (inputRegion t input.1 input.2, false) ∈ abiBuffers s ++ [(abiArguments s, false)] :=
+ List.mem_append_left _ (List.mem_append_left _ (List.mem_map.mpr ⟨_, region, rfl⟩))
+ refine ⟨space, pointerSlot, lengthSlot, pointerBound, lengthBound,
+ abi_input_lengths h _ region, ?_, ?_, ?_⟩
+ · intro p n ⟨r, hr, hc⟩
+ simp only [List.mem_singleton] at hr; subst r
+ refine ⟨_, List.mem_append_left _ ?_, hc⟩
+ rw [prepared.rd]
+ change inputRegion t input.1 input.2 ∈ (frameStart s Impl.Argon2.X86_64.Derive.saved).rd
+ rw [frameStart_rd, h.rd]
+ exact List.mem_append_left _ region
+ · rw [scratch]; exact separation.inputWork _ region
+ · exact (private_stack_disjoint h prepared (inputRegion t input.1 input.2, false) buffer 16 (by decide)).symm
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashSpace.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashSpace.lean
new file mode 100644
index 000000000..ac7904bcb
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHashSpace.lean
@@ -0,0 +1,24 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveWords
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveSeparation
+
+/-! Permissions for H₀ follow from the signature and the private ABI frame. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_hash_space {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : Initial.Space t := by
+ have scratch := private_scratch h prepared
+ have member : (abiWork s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ refine ⟨?_, ?_, ?_, private_frame_stack prepared 16 (by decide), ?_,
+ by simpa only [BitVec.add_zero] using private_local_write prepared 0 64 (by decide)⟩
+ · rw [scratch]; exact private_work_member h prepared
+ · rw [scratch]; exact private_stack_disjoint h prepared (abiWork s, true) member 16 (by decide)
+ · rw [scratch]; exact private_frame_disjoint h prepared (abiWork s, true) member
+ · intro d hd
+ have bounds : ∀ d ∈ Initial.slots, d + 8 ≤ 272 := by decide
+ exact private_local_read prepared d 8 (bounds d hd)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHeader.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHeader.lean
new file mode 100644
index 000000000..4765d0b39
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveHeader.lean
@@ -0,0 +1,25 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveWords
+
+/-! H₀ hashes the original requested memory cost and the exact reviewed parameters. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_header {s t : State} (words : DeriveWords s t) :
+ Initial.headerBytes t = Proof.Argon2.initialHeader (abiParams s) := by
+ have headerWords : (List.range 6).map (Initial.headerValue t) =
+ [BitVec.ofNat 32 (abiParams s).lanes, BitVec.ofNat 32 (abiParams s).tagLen,
+ BitVec.ofNat 32 (abiParams s).memory, BitVec.ofNat 32 (abiParams s).passes,
+ 19#32, BitVec.ofNat 32 (abiParams s).variant.code] := by
+ change [(Initial.wordAt t 184).setWidth 32, (Initial.wordAt t 264).setWidth 32,
+ (Initial.wordAt t 176).setWidth 32, (Initial.wordAt t 72).setWidth 32, 19#32,
+ (Initial.wordAt t 112).setWidth 32] = _
+ rw [words.lanes, words.tagLength, words.memory, words.passes, words.kind]
+ simp only [BitVec.setWidth_ofNat_of_le (show 32 ≤ 64 by decide)]
+ unfold Initial.headerBytes
+ rw [← List.flatMap_map, headerWords]
+ simp only [List.flatMap_cons, List.flatMap_nil, List.append_nil, ← List.append_assoc,
+ Proof.Argon2.initialHeader, Spec.Argon2.le32]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveInputBytes.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveInputBytes.lean
new file mode 100644
index 000000000..f2b66e6e7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveInputBytes.lean
@@ -0,0 +1,50 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveBodyReady
+
+/-! Saving registers and copying arguments leave all original input bytes unchanged. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_prepare_frame {s t : State} (prepared : PrivatePrepared (prologueState s) t) :
+ Frame [⟨(prologueState s).gpr .rsp, 272⟩] (prologueState s).mem t.mem := by
+ apply prepared.frame.sub
+ intro region hr
+ simp only [privateWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact ⟨_, List.mem_singleton_self _, Region.sub_prefix (by decide)⟩
+ · exact ⟨_, List.mem_singleton_self _, Offset.sub_base _ (by decide)⟩
+
+theorem private_prologue_frame {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : Frame [below (s.gpr .rsp) 320] s.mem t.mem := by
+ have prologue := frameStart_frame s Impl.Argon2.X86_64.Derive.saved (by decide) (by
+ have space := h.stack; change 320 ≤ (s.gpr .rsp).toNat; omega)
+ apply prologue.trans
+ have preparation := private_prepare_frame prepared
+ rw [prologue_sp] at preparation
+ exact preparation.sub (by
+ intro region hr; simp only [List.mem_singleton] at hr; subst region
+ exact ⟨_, List.mem_singleton_self _, Region.sub_prefix (by decide)⟩)
+
+theorem private_input_bytes {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (input : Nat × Nat) (hi : input ∈ Initial.inputs) :
+ Initial.inputBytes t input.1 input.2 =
+ Spec.Blake2.bytesAt s.mem (Initial.inputRegion t input.1 input.2).base
+ (Initial.inputRegion t input.1 input.2).len := by
+ have words := private_words h prepared
+ have region := private_input_member words input hi
+ have buffer : (Initial.inputRegion t input.1 input.2, false) ∈ abiBuffers s ++ [(abiArguments s, false)] :=
+ List.mem_append_left _ (List.mem_append_left _ (List.mem_map.mpr ⟨_, region, rfl⟩))
+ have disjoint := h.reserved (below (s.gpr .rsp) 344)
+ (List.mem_cons_of_mem _ (List.mem_singleton_self _))
+ (Initial.inputRegion t input.1 input.2, false) buffer
+ have length := abi_input_lengths h _ region
+ apply Proof.Blake2.bytesAt_congr
+ intro i hi'
+ apply (private_prologue_frame h prepared).bytes (R := Initial.inputRegion t input.1 input.2)
+ _ (by omega) hi'
+ intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ exact (disjoint.sub_left (below_sub (by decide) (by decide))).symm
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveLit.lean
new file mode 100644
index 000000000..2bbd1d8ca
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveLit.lean
@@ -0,0 +1,19 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Derive
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressLit
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapLit
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointersLit
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressHeaderLit
+import VerifiedGarbage.Proof.Argon2.X86_64.ClearBlockLit
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceBlockLit
+import VerifiedGarbage.Proof.Framework.Lit
+
+/-! Checked literals for the entry point's fixed instruction shapes. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.Derive.prepare
+materialize_code Impl.Argon2.X86_64.FillSetup.code
+materialize_code Impl.Argon2.X86_64.FillIterations.loop
+materialize_code Impl.Argon2.X86_64.FinalReduction.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMemorySpace.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMemorySpace.lean
new file mode 100644
index 000000000..89fca22bf
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMemorySpace.lean
@@ -0,0 +1,37 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveAllocations
+
+/-! The signature's rounded block allocation supplies all initialization permissions. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_matrix_bytes {s : State} (h : AbiEnvironment s) :
+ 1024 * ((abiParams s).lanes * (abiParams s).laneLen) = (abiParams s).blocks * 1024 := by
+ rw [← Proof.Argon2.blocks_lanes (abiParams s) h.valid.1, Nat.mul_comm]
+
+theorem private_memory_space {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ MemoryInit.Space t (FillKernel.matrix t)
+ (1024 * ((abiParams s).lanes * (abiParams s).laneLen)) := by
+ have matrix := private_matrix_region h prepared
+ have separation := abi_separation h
+ have scratch := private_scratch h prepared
+ have matrixMember : (abiMatrix s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ have workMember : (abiWork s, true) ∈ abiBuffers s ++ [(abiArguments s, false)] := by
+ simp only [abiBuffers, List.mem_append, List.mem_cons, true_or, or_true]
+ rw [private_matrix_bytes h]
+ refine ⟨private_matrix_cover h prepared, private_local_cover prepared 72 (by decide), ?_,
+ ?_, ?_, ?_, (private_frame_stack prepared 24 (by decide)).symm, ?_, ?_, ?_⟩
+ · rw [scratch]; exact private_work_member h prepared
+ · rw [matrix]; exact private_frame_disjoint h prepared (abiMatrix s, true) matrixMember
+ · rw [scratch]; exact private_frame_disjoint h prepared (abiWork s, true) workMember
+ · rw [matrix, scratch]; exact separation.matrixWork
+ · rw [matrix]; exact private_stack_disjoint h prepared (abiMatrix s, true) matrixMember 24 (by decide)
+ · rw [scratch]; exact private_stack_disjoint h prepared (abiWork s, true) workMember 24 (by decide)
+ · have blocks := Proof.Argon2.blocks_le_memory (abiParams s)
+ have memoryBound := h.valid.2.2.2.2.2.1
+ omega
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMetadata.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMetadata.lean
new file mode 100644
index 000000000..09901146f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMetadata.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePrologue
+import VerifiedGarbage.Proof.Argon2.X86_64.Parameters
+
+/-! The private frame contains the exact arguments decoded by the shared contract. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem private_stack_word {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (j : Nat) (hj : j < 12) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 (copyDestination j)) 64 =
+ let word := abiWord s (8 * (j + 1))
+ if j < 3 then (word.setWidth 32).setWidth 64 else word := by
+ rw [prepared.stackWords j hj, prologue_word h j hj]
+
+theorem private_argument_word {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (arg : Nat × Reg) (member : arg ∈ arguments) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 arg.1) 64 = argumentValue s arg.2 := by
+ rw [prepared.values arg member]
+ unfold argumentValue
+ have notSp : ∀ arg ∈ arguments, arg.2 ≠ .rsp := by decide
+ unfold prologueState
+ rw [frameStart_reg s _ arg.2 (notSp arg member)]
+
+theorem private_local_write {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (d n : Nat) (bound : d + n ≤ 272) : InRegions t.wr (t.gpr .rbp + BitVec.ofNat 64 d) n := by
+ rw [prepared.wr, prepared.bp]
+ apply prologue_locals s
+ exact ⟨_, List.mem_singleton_self _, Offset.contains_base _ bound (by omega)⟩
+
+theorem private_local_read {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (d n : Nat) (bound : d + n ≤ 272) :
+ InRegions (t.rd ++ t.wr) (t.gpr .rbp + BitVec.ofNat 64 d) n := by
+ obtain ⟨r, hr, hc⟩ := private_local_write prepared d n bound
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+
+theorem private_parameters {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : Parameters.Ready (abiParams s) t := by
+ refine ⟨private_local_read prepared 176 8 (by decide), private_local_read prepared 184 8 (by decide),
+ ?_, ?_, h.valid.1, h.valid.2.2.2.2.2.1, h.valid.2.1⟩
+ · have word := private_stack_word h prepared 0 (by decide)
+ change t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 176) 64 =
+ (((abiWord s 8).setWidth 32).setWidth 64) at word
+ change t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 176) 64 =
+ BitVec.ofNat 64 ((abiWord s 8).setWidth 32).toNat
+ rw [word, BitVec.ofNat_toNat]
+ · have word := private_stack_word h prepared 1 (by decide)
+ change t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 184) 64 =
+ (((abiWord s 16).setWidth 32).setWidth 64) at word
+ change t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 184) 64 =
+ BitVec.ofNat 64 ((abiWord s 16).setWidth 32).toNat
+ rw [word, BitVec.ofNat_toNat]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMxcsr.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMxcsr.lean
new file mode 100644
index 000000000..dac0de588
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveMxcsr.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveLit
+import VerifiedGarbage.Proof.Argon2.X86_64.HPrime.Correct
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialLit
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitLit
+import VerifiedGarbage.Proof.Argon2.X86_64.ParametersLit
+
+/-! The complete derivation preserves MXCSR for every BLAKE2b backend. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+local notation "property" => (fun i => !loadsMxcsr i)
+
+theorem initial_mxcsr (v : Proof.Blake2.X86_64.Backend) :
+ (Impl.Argon2.X86_64.Initial.code (HPrime.hash v)).allInstrs property = true := by
+ have init : (HPrime.hash v).init.allInstrs property = true := by
+ change (Impl.Blake2.X86_64.Stream.init Spec.Blake2.b).allInstrs _ = true
+ lit_decide
+ have update : (HPrime.hash v).update.allInstrs property = true := v.updateMxcsr
+ have finalize : (HPrime.hash v).finalize.allInstrs property = true := v.finalizeMxcsr
+ simp only [Impl.Argon2.X86_64.Initial.code, Impl.Argon2.X86_64.Initial.start,
+ Impl.Argon2.X86_64.Initial.absorb, Impl.Argon2.X86_64.Initial.finish,
+ Impl.Argon2.X86_64.HPrime.init, Impl.Argon2.X86_64.HPrime.absorbFixed,
+ Impl.Argon2.X86_64.HPrime.update, Impl.Argon2.X86_64.HPrime.finalize, Code.allInstrs]
+ rw [init, update, finalize]
+ lit_decide
+
+theorem memory_mxcsr (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ (Impl.Argon2.X86_64.MemoryInit.code name (HPrime.hash v)).allInstrs property = true := by
+ simp only [Impl.Argon2.X86_64.MemoryInit.code, Impl.Argon2.X86_64.MemoryInit.clear,
+ Impl.Argon2.X86_64.MemoryInit.lane, Impl.Argon2.X86_64.MemoryInit.block, Code.allInstrs]
+ rw [HPrime.code_mxcsr v]
+ lit_decide
+
+theorem frame_mxcsr (body : Prog isa) (rs : List Reg) (h : body.allInstrs property = true) :
+ (Impl.Argon2.X86_64.Derive.frame body rs).allInstrs property = true := by
+ induction rs with
+ | nil => simpa [Impl.Argon2.X86_64.Derive.frame, Code.allInstrs, loadsMxcsr] using h
+ | cons r rs ih => simpa [Impl.Argon2.X86_64.Derive.frame, Code.allInstrs, loadsMxcsr] using ih
+
+theorem code_mxcsr (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v)).allInstrs property = true := by
+ unfold Impl.Argon2.X86_64.Derive.code
+ apply frame_mxcsr
+ simp only [Impl.Argon2.X86_64.Derive.body, Impl.Argon2.X86_64.InitialBody.code,
+ Impl.Argon2.X86_64.InitFill.code, Impl.Argon2.X86_64.FillFinish.code,
+ Impl.Argon2.X86_64.Finish.code, Impl.Argon2.X86_64.FinalOutput.code, Code.allInstrs]
+ rw [initial_mxcsr v, memory_mxcsr v name, HPrime.code_mxcsr v]
+ lit_decide
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalize.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalize.lean
new file mode 100644
index 000000000..1b9b1067f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalize.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Derive
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialArgs
+import VerifiedGarbage.Proof.Framework.Offset
+
+/-! Normalize u32 stack arguments without assuming anything about their upper bits. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+open VG.Impl.Argon2.X86_64.Derive
+
+structure Normalized (s t : State) (d : Nat) : Prop where
+ mem : t.mem = s.mem.writeW (s.gpr .rbp + BitVec.ofNat 64 d)
+ (((s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 d) 64).setWidth 32).setWidth 64)
+ regs : ∀ r, r ≠ .rax → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem normalize_ok (s : State) (d : Nat)
+ (read : InRegions (s.rd ++ s.wr) (s.gpr .rbp + BitVec.ofNat 64 d) 8)
+ (write : InRegions s.wr (s.gpr .rbp + BitVec.ofNat 64 d) 8) :
+ WP isa (.block (normalize d)) s (Normalized s · d) := by
+ apply WP.of_runBlock
+ simp only [normalize, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ readSrc32, State.load64, State.store64, State.setReg32, State.ea, Impl.Argon2.X86_64.at_, BitVec.ofInt_natCast,
+ RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ read, write, reduceCtorEq, ite_true, ite_false, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨rfl, ?_, rfl, rfl, rfl⟩
+ intro r hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+
+theorem Normalized.word {s t : State} {d : Nat} (h : Normalized s t d) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 =
+ (((s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 d) 64).setWidth 32).setWidth 64) := by
+ rw [h.regs .rbp (by decide), h.mem, Mem.readW_writeW_self64]
+
+theorem Normalized.frame {s t : State} {d : Nat} (h : Normalized s t d) :
+ Frame [⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩] s.mem t.mem := by
+ rw [h.mem]
+ exact (Frame.refl _ _).writeW (List.mem_singleton_self _) _ (Region.contains_self _ _)
+
+theorem Normalized.other_word {s t : State} {d : Nat} (h : Normalized s t d)
+ (e : Nat) (separate : e + 8 ≤ d ∨ d + 8 ≤ e) (ed : e + 8 < 2 ^ 64) (dd : d + 8 < 2 ^ 64) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 e) 64 = s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 e) 64 := by
+ rw [h.regs .rbp (by decide), h.mem]
+ exact Mem.readW_writeW_sep (Offset.sep _ separate (Nat.le_of_lt ed) (Nat.le_of_lt dd)) (by decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalizeArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalizeArgs.lean
new file mode 100644
index 000000000..3a0a507db
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveNormalizeArgs.lean
@@ -0,0 +1,75 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveNormalize
+
+/-! Normalize distinct stack slots while retaining every other frame word. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def normalizedWord (s : State) (d : Nat) : Addr :=
+ (((s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 d) 64).setWidth 32).setWidth 64)
+
+structure NormalizedArgs (s t : State) (ds : List Nat) : Prop where
+ values : ∀ d ∈ ds, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 = normalizedWord s d
+ regs : ∀ r, r ≠ .rax → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ frame : Frame (ds.map fun d => (⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩ : Region)) s.mem t.mem
+
+theorem NormalizedArgs.other_word {s t : State} {ds : List Nat} (h : NormalizedArgs s t ds)
+ (e : Nat) (bound : e + 8 < 2 ^ 64)
+ (separate : ∀ d ∈ ds, e + 8 ≤ d ∨ d + 8 ≤ e)
+ (bounds : ∀ d ∈ ds, d + 8 < 2 ^ 64) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 e) 64 = s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 e) 64 := by
+ rw [h.regs .rbp (by decide)]
+ apply h.frame.readW (r := ⟨s.gpr .rbp + BitVec.ofNat 64 e, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro region hr
+ obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hr
+ exact Offset.disjoint _ (separate d hd) (Nat.le_of_lt bound) (Nat.le_of_lt (bounds d hd))
+
+theorem normalizeArgs_ok (ds : List Nat) (s : State)
+ (read : ∀ d ∈ ds, InRegions (s.rd ++ s.wr) (s.gpr .rbp + BitVec.ofNat 64 d) 8)
+ (write : ∀ d ∈ ds, InRegions s.wr (s.gpr .rbp + BitVec.ofNat 64 d) 8)
+ (separate : ds.Pairwise fun d e => d + 8 ≤ e ∨ e + 8 ≤ d)
+ (bounds : ∀ d ∈ ds, d + 8 < 2 ^ 64) :
+ WP isa (Impl.Argon2.X86_64.Derive.normalizeArgs ds) s (NormalizedArgs s · ds) := by
+ induction ds generalizing s with
+ | nil => exact WP.block_nil ⟨by simp, fun _ _ => rfl, rfl, rfl, rfl, Frame.refl _ _⟩
+ | cons d ds ih =>
+ cases ds with
+ | nil =>
+ refine (normalize_ok s d (read d (by simp)) (write d (by simp))).mono ?_
+ intro t ht
+ exact ⟨fun e he => by simp only [List.mem_singleton] at he; subst e; exact ht.word,
+ ht.regs, ht.rd, ht.wr, ht.mxcsr, ht.frame⟩
+ | cons e ds =>
+ obtain ⟨headSep, tailSep⟩ := List.pairwise_cons.mp separate
+ refine WP.seq ((normalize_ok s d (read d (List.mem_cons_self ..))
+ (write d (List.mem_cons_self ..))).mono ?_)
+ intro t ht
+ refine (ih t
+ (fun x hx => by rw [ht.rd, ht.wr, ht.regs .rbp (by decide)]; exact read x (List.mem_cons_of_mem d hx))
+ (fun x hx => by rw [ht.wr, ht.regs .rbp (by decide)]; exact write x (List.mem_cons_of_mem d hx))
+ tailSep (fun x hx => bounds x (List.mem_cons_of_mem d hx))).mono ?_
+ intro u hu
+ refine ⟨?_, fun r hr => (hu.regs r hr).trans (ht.regs r hr),
+ hu.rd.trans ht.rd, hu.wr.trans ht.wr, hu.mxcsr.trans ht.mxcsr, ?_⟩
+ · intro x hx
+ rcases List.mem_cons.mp hx with rfl | hx
+ · rw [hu.other_word x (bounds x (List.mem_cons_self ..)) headSep
+ (fun a ha => bounds a (List.mem_cons_of_mem x ha))]
+ exact ht.word
+ · rw [hu.values x hx]
+ unfold normalizedWord
+ have sep : x + 8 ≤ d ∨ d + 8 ≤ x := (headSep x hx).symm
+ rw [ht.other_word x sep (bounds x (List.mem_cons_of_mem d hx)) (bounds d (List.mem_cons_self ..))]
+ · apply (ht.frame.mono ?_).trans
+ · have frame := hu.frame
+ rw [ht.regs .rbp (by decide)] at frame
+ exact frame.mono (fun _ h => List.mem_cons_of_mem _ h)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ exact List.mem_cons_self ..
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParameters.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParameters.lean
new file mode 100644
index 000000000..2ec676de1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParameters.lean
@@ -0,0 +1,67 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.Parameters
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyState
+
+/-! Compute the rounded lane length before entering the complete Argon2 body. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Impl.Argon2.X86_64.Initial
+
+/-- A specification state for the body's readiness predicate, not executable code. -/
+def dimensionState (s : State) (p : Params) : State :=
+ s.setReg .r13 (BitVec.ofNat 64 p.laneLen)
+
+theorem dimension_frame (s : State) (p : Params) : InitialBody.SameFrame s (dimensionState s p) := by
+ refine ⟨?_, ?_, ?_, ?_, ?_, ?_⟩
+ · exact RegUpd.gpr_setReg_of_ne _ _ (by decide)
+ · exact RegUpd.gpr_setReg_of_ne _ _ (by decide)
+ · exact RegUpd.gpr_setReg_of_ne _ _ (by decide)
+ · exact RegUpd.mem_setReg ..
+ · exact RegUpd.rd_setReg ..
+ · exact RegUpd.wr_setReg ..
+
+theorem parameters_frame {s t : State} (p : Params)
+ (keeps : Divide.Keeps Parameters.changed s t) :
+ InitialBody.SameFrame (dimensionState s p) t := by
+ have frame := dimension_frame s p
+ refine ⟨?_, ?_, ?_, keeps.mem.trans frame.mem.symm,
+ keeps.rd.trans frame.rd.symm, keeps.wr.trans frame.wr.symm⟩
+ · exact (keeps.regs .rbp (by decide)).trans frame.bp.symm
+ · exact (keeps.regs .rbx (by decide)).trans frame.bx.symm
+ · exact (keeps.regs .rsp (by decide)).trans frame.sp.symm
+
+theorem parameters_ready {s t : State} {p : Params}
+ (h : InitialBody.Ready p (dimensionState s p))
+ (length : t.gpr .r13 = BitVec.ofNat 64 p.laneLen)
+ (keeps : Divide.Keeps Parameters.changed s t) : InitialBody.Ready p t :=
+ h.of_state (parameters_frame p keeps) length
+
+theorem parameters_body_ok (v : Proof.Blake2.X86_64.Backend) (name : String)
+ (s : State) (p : Params) (parameters : Parameters.Ready p s)
+ (body : InitialBody.Ready p (dimensionState s p)) :
+ WP isa (.seq Impl.Argon2.X86_64.Parameters.code
+ (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v))) s (InitialBody.Done s · p) := by
+ refine WP.seq ((Parameters.code_ok s p parameters).mono ?_)
+ rintro a ⟨length, keeps⟩
+ refine (InitialBody.code_ok v name a p (parameters_ready body length keeps)).mono ?_
+ intro t done
+ have bp := keeps.regs .rbp (by decide)
+ have sp := keeps.regs .rsp (by decide)
+ have base : FillKernel.matrix a = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [keeps.mem, bp]
+ have work : FinalOutput.work a = FinalOutput.work s := by
+ unfold FinalOutput.work; rw [keeps.mem, bp]
+ have output : FinalOutput.output a = FinalOutput.output s := by
+ unfold FinalOutput.output; rw [keeps.mem, bp]
+ refine ⟨?_, done.bp.trans bp, done.sp.trans sp,
+ done.rd.trans keeps.rd, done.wr.trans keeps.wr, ?_⟩
+ · have digest := done.digest
+ simp only [Initial.inputBytes, Initial.wordAt, keeps.mem, bp, output] at digest
+ exact digest
+ · have frame := done.frame
+ rw [InitFill.writes_eq s a p bp sp base work output] at frame
+ rw [keeps.mem] at frame
+ exact frame
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParametersCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParametersCT.lean
new file mode 100644
index 000000000..7721e7209
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveParametersCT.lean
@@ -0,0 +1,31 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveParameters
+import VerifiedGarbage.Proof.Argon2.X86_64.ParametersCT
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyReviewedState
+
+/-! Parameter calculation followed by the complete body obeys the reviewed leakage. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure ParametersRelated (p : Params) (s t : State) : Prop where
+ left : Parameters.Ready p s
+ right : Parameters.Ready p t
+ body : InitialBody.ReviewedRelated p (dimensionState s p) (dimensionState t p)
+
+theorem parameters_body_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params) :
+ RelCT isa (ParametersRelated p)
+ (.seq Impl.Argon2.X86_64.Parameters.code
+ (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v))) (fun _ _ => True) := by
+ have preparation := (Parameters.code_rel.mono (P' := ParametersRelated p)
+ (fun s t h => by
+ have left := dimension_frame s p
+ have right := dimension_frame t p
+ exact left.bp.symm.trans (h.body.hashing.bp.trans right.bp))
+ (fun _ _ h => h)).wpDep (fun s t h =>
+ ⟨Parameters.code_ok s p h.left, Parameters.code_ok t p h.right⟩)
+ refine preparation.seq ((InitialBody.reviewed_rel v name p).mono ?_ (fun _ _ h => h))
+ rintro a b ⟨_, s, t, h, ⟨length₁, keeps₁⟩, ⟨length₂, keeps₂⟩⟩
+ exact h.body.of_state (parameters_frame p keeps₁) (parameters_frame p keeps₂) length₁ length₂
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepare.lean
new file mode 100644
index 000000000..98e27ec37
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepare.lean
@@ -0,0 +1,92 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveNormalizeArgs
+
+/-! Complete ABI preparation retains the inputs and exposes normalized public arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def normalizedOffsets : List Nat := [176, 184, 192]
+
+def prepareWrites (s : State) : List Region :=
+ ⟨s.gpr .rsp, 120⟩ :: normalizedOffsets.map fun d => ⟨s.gpr .rsp + BitVec.ofNat 64 d, 8⟩
+
+theorem SetupDone.other_word {s t : State} (h : SetupDone s t) (d : Nat)
+ (afterFrame : 120 ≤ d) (bound : d + 8 < 2 ^ 64) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 = s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 d) 64 := by
+ rw [h.bp]
+ apply h.frame.readW (r := ⟨s.gpr .rsp + BitVec.ofNat 64 d, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ simpa only [BitVec.add_zero] using Offset.disjoint (s.gpr .rsp) (d := d) (n := 8) (e := 0) (k := 120)
+ (Or.inr afterFrame) (Nat.le_of_lt bound) (by decide)
+
+structure Prepared (s t : State) : Prop where
+ bp : t.gpr .rbp = s.gpr .rsp
+ sp : t.gpr .rsp = s.gpr .rsp
+ scratch : t.gpr .rbx = s.mem.readW (s.gpr .rsp + 248) 64
+ values : ∀ arg ∈ arguments, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 arg.1) 64 = argumentValue s arg.2
+ normalized : ∀ d ∈ normalizedOffsets, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 =
+ (((s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 d) 64).setWidth 32).setWidth 64)
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbp → r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ frame : Frame (prepareWrites s) s.mem t.mem
+
+theorem Prepared.other_word {s t : State} (h : Prepared s t) (d : Nat)
+ (afterFrame : 120 ≤ d) (bound : d + 8 < 2 ^ 64)
+ (separate : ∀ e ∈ normalizedOffsets, d + 8 ≤ e ∨ e + 8 ≤ d) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 =
+ s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 d) 64 := by
+ rw [h.bp]
+ apply h.frame.readW (r := ⟨s.gpr .rsp + BitVec.ofNat 64 d, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro region hr
+ rcases List.mem_cons.mp hr with rfl | hr
+ · simpa only [BitVec.add_zero] using Offset.disjoint (s.gpr .rsp) (d := d) (n := 8) (e := 0) (k := 120)
+ (Or.inr afterFrame) (Nat.le_of_lt bound) (by decide)
+ · obtain ⟨e, he, rfl⟩ := List.mem_map.mp hr
+ have bounds : ∀ e ∈ normalizedOffsets, e + 8 ≤ 2 ^ 64 := by decide
+ exact Offset.disjoint _ (separate e he) (Nat.le_of_lt bound) (bounds e he)
+
+theorem prepareLocal_ok (s : State) (frameWrite : Covers [⟨s.gpr .rsp, 120⟩] s.wr)
+ (read : ∀ d ∈ 248 :: normalizedOffsets, InRegions (s.rd ++ s.wr) (s.gpr .rsp + BitVec.ofNat 64 d) 8)
+ (write : ∀ d ∈ normalizedOffsets, InRegions s.wr (s.gpr .rsp + BitVec.ofNat 64 d) 8) :
+ WP isa Impl.Argon2.X86_64.Derive.prepareLocal s (Prepared s) := by
+ unfold Impl.Argon2.X86_64.Derive.prepareLocal
+ have scratchRead : InRegions (s.rd ++ s.wr) (s.gpr .rsp + 248) 8 := read 248 (List.mem_cons_self ..)
+ refine WP.seq ((setup_ok s frameWrite scratchRead).mono ?_)
+ intro a setup
+ refine (normalizeArgs_ok normalizedOffsets a
+ (fun d hd => by rw [setup.rd, setup.wr, setup.bp]; exact read d (List.mem_cons_of_mem _ hd))
+ (fun d hd => by rw [setup.wr, setup.bp]; exact write d hd) (by decide) (by decide)).mono ?_
+ intro t normalized
+ refine ⟨(normalized.regs .rbp (by decide)).trans setup.bp,
+ (normalized.regs .rsp (by decide)).trans setup.sp,
+ (normalized.regs .rbx (by decide)).trans setup.scratch, ?_, ?_, ?_,
+ normalized.rd.trans setup.rd, normalized.wr.trans setup.wr, normalized.mxcsr.trans setup.mxcsr, ?_⟩
+ · intro arg ha
+ have bound : ∀ arg ∈ arguments, arg.1 + 8 < 2 ^ 64 := by decide
+ have separate : ∀ arg ∈ arguments, ∀ d ∈ normalizedOffsets, arg.1 + 8 ≤ d ∨ d + 8 ≤ arg.1 := by decide
+ rw [normalized.other_word arg.1 (bound arg ha) (separate arg ha) (by decide)]
+ exact setup.values arg ha
+ · intro d hd
+ rw [normalized.values d hd]
+ unfold normalizedWord
+ have afterFrame : ∀ d ∈ normalizedOffsets, 120 ≤ d := by decide
+ have bound : ∀ d ∈ normalizedOffsets, d + 8 < 2 ^ 64 := by decide
+ rw [setup.other_word d (afterFrame d hd) (bound d hd)]
+ · intro r hr hb hx
+ have notAx : ∀ r ∈ calleeSaved, r ≠ .rax := by decide
+ exact (normalized.regs r (notAx r hr)).trans (setup.regs r hr hb hx)
+ · apply (setup.frame.mono (by
+ intro region hr
+ simp only [List.mem_singleton] at hr
+ subst region
+ exact List.mem_cons_self ..)).trans
+ have frame := normalized.frame
+ rw [setup.bp] at frame
+ exact frame.mono (fun _ h => List.mem_cons_of_mem _ h)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepareCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepareCT.lean
new file mode 100644
index 000000000..fba199fbe
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrepareCT.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveNormalize
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! ABI argument preparation accesses only fixed offsets of the public stack. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem prepare_rel : RelCT isa (fun s t => s.gpr .rsp = t.gpr .rsp)
+ Impl.Argon2.X86_64.Derive.prepare (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rsp]) (fun _ _ h =>
+ Taint.agree_ofRegs (by intro r hr; simp only [List.mem_singleton] at hr; subst r; exact h))
+ (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePrepare.lean
new file mode 100644
index 000000000..de6da64b9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePrepare.lean
@@ -0,0 +1,98 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveCopyArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePrepare
+
+/-! Prepare private copies of every ABI argument, preserving caller-owned storage. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def privateWrites (s : State) : List Region := [⟨s.gpr .rsp, 120⟩, ⟨s.gpr .rsp + 176, 96⟩]
+
+structure PrivatePrepared (s t : State) : Prop where
+ bp : t.gpr .rbp = s.gpr .rsp
+ sp : t.gpr .rsp = s.gpr .rsp
+ scratch : t.gpr .rbx = s.mem.readW (s.gpr .rsp + 400) 64
+ values : ∀ arg ∈ arguments, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 arg.1) 64 = argumentValue s arg.2
+ stackWords : ∀ j < 12, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 (copyDestination j)) 64 =
+ let w := s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 64
+ if j < 3 then (w.setWidth 32).setWidth 64 else w
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbp → r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (privateWrites s) s.mem t.mem
+
+theorem private_prepare_ok (s : State) (locals : Covers [⟨s.gpr .rsp, 272⟩] s.wr)
+ (read : ∀ j < 12, InRegions (s.rd ++ s.wr) (s.gpr .rsp + BitVec.ofNat 64 (copySource j)) 8) :
+ WP isa Impl.Argon2.X86_64.Derive.prepare s (PrivatePrepared s) := by
+ have localWord : ∀ d, d + 8 ≤ 272 → InRegions s.wr (s.gpr .rsp + BitVec.ofNat 64 d) 8 := by
+ intro d hd
+ exact locals _ _ ⟨_, List.mem_singleton_self _, Offset.contains_base _ hd (by omega)⟩
+ unfold Impl.Argon2.X86_64.Derive.prepare Impl.Argon2.X86_64.Derive.copyArgs
+ refine WP.seq ((copyArgs_ok (List.range 12) s
+ (fun _ h => List.mem_range.mp h) List.nodup_range (fun j h => read j (List.mem_range.mp h))
+ (fun j h => localWord _ (by have := List.mem_range.mp h; unfold copyDestination; omega))).mono ?_)
+ intro a copied
+ have sp := copied.regs .rsp (by decide)
+ refine (prepareLocal_ok a (by
+ intro p n h
+ rw [copied.wr]
+ rw [sp] at h
+ apply locals p n
+ exact Covers.of_sub (by
+ intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact ⟨_, List.mem_singleton_self _, 0, (BitVec.add_zero _).symm, show 0 + 120 ≤ 272 by decide⟩) p n h)
+ (by
+ intro d hd
+ rw [copied.rd, copied.wr, sp]
+ have bound : ∀ d ∈ 248 :: normalizedOffsets, d + 8 ≤ 272 := by decide
+ obtain ⟨region, member, contains⟩ := localWord d (bound d hd)
+ exact ⟨region, List.mem_append_right _ member, contains⟩)
+ (by
+ intro d hd; rw [copied.wr, sp]
+ have bound : ∀ d ∈ normalizedOffsets, d + 8 ≤ 272 := by decide
+ exact localWord d (bound d hd))).mono ?_
+ intro t prepared
+ refine ⟨prepared.bp.trans sp, prepared.sp.trans sp, ?_, ?_, ?_, ?_,
+ prepared.rd.trans copied.rd, prepared.wr.trans copied.wr, ?_⟩
+ · have word := copied.values 9 (by decide)
+ change a.mem.readW (a.gpr .rsp + 248) 64 = s.mem.readW (s.gpr .rsp + 400) 64 at word
+ exact prepared.scratch.trans word
+ · intro arg ha
+ rw [prepared.values arg ha]
+ unfold argumentValue
+ have notAx : ∀ arg ∈ arguments, arg.2 ≠ .rax := by decide
+ rw [copied.regs arg.2 (notAx arg ha)]
+ · intro j hj
+ by_cases small : j < 3
+ · have slot : ∀ j < 3, copyDestination j ∈ normalizedOffsets := by decide
+ rw [prepared.normalized _ (slot j small), copied.values j (List.mem_range.mpr hj), ite_eq_left small]
+ · rw [ite_eq_right small, prepared.other_word _ (by unfold copyDestination; omega)
+ (by unfold copyDestination; omega) (by
+ intro d hd
+ have upper : ∀ d ∈ normalizedOffsets, d + 8 ≤ 200 := by decide
+ have := upper d hd; unfold copyDestination; omega)]
+ exact copied.values j (List.mem_range.mpr hj)
+ · intro r hr hb hx
+ have notAx : ∀ r ∈ calleeSaved, r ≠ .rax := by decide
+ exact (prepared.regs r hr hb hx).trans (copied.regs r (notAx r hr))
+ · apply (copied.frame.sub ?_).trans (prepared.frame.sub ?_)
+ · intro region hr
+ obtain ⟨j, hj, rfl⟩ := List.mem_map.mp hr
+ have bound := List.mem_range.mp hj
+ refine ⟨⟨s.gpr .rsp + 176, 96⟩, List.mem_cons_of_mem _ (List.mem_singleton_self _), ?_⟩
+ unfold copyDestination
+ rw [BitVec.ofNat_add, ← BitVec.add_assoc]
+ exact Offset.sub_base _ (by omega)
+ · intro region hr
+ rcases List.mem_cons.mp hr with rfl | hr
+ · rw [sp]; exact ⟨_, List.mem_cons_self .., fun _ h => h⟩
+ · obtain ⟨d, hd, rfl⟩ := List.mem_map.mp hr
+ rw [sp]
+ have bounds : ∀ d ∈ normalizedOffsets, 176 ≤ d ∧ d + 8 ≤ 272 := by decide
+ obtain ⟨lo, hi⟩ := bounds d hd
+ refine ⟨⟨s.gpr .rsp + 176, 96⟩, List.mem_cons_of_mem _ (List.mem_singleton_self _), ?_⟩
+ rw [show d = 176 + (d - 176) by omega, BitVec.ofNat_add, ← BitVec.add_assoc]
+ exact Offset.sub_base _ (by omega)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePublic.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePublic.lean
new file mode 100644
index 000000000..855ee3c71
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrivatePublic.lean
@@ -0,0 +1,91 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePublic
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveInputBytes
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveParametersCT
+
+/-! Private argument copies retain exactly the reviewed public relation. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+open VG.Proof.Argon2.X86_64.Initial (wordAt)
+
+theorem DeriveWords.public_words {s₁ s₂ t₁ t₂ : State} (h : AbiPublic s₁ s₂)
+ (left : DeriveWords s₁ t₁) (right : DeriveWords s₂ t₂) :
+ ∀ d ∈ Initial.slots, wordAt t₁ d = wordAt t₂ d := by
+ intro d hd
+ simp only [Initial.slots, List.mem_cons, List.not_mem_nil, or_false] at hd
+ rcases hd with rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl | rfl
+ · rw [left.passes, right.passes, h.params]
+ · rw [left.saltLength, right.saltLength]; exact h.regs .r8 (by simp)
+ · rw [left.salt, right.salt]; exact h.regs .rcx (by simp)
+ · rw [left.passwordLength, right.passwordLength]; exact h.regs .rdx (by simp)
+ · rw [left.password, right.password]; exact h.regs .rsi (by simp)
+ · rw [left.kind, right.kind, h.params]
+ · rw [left.memory, right.memory, h.params]
+ · rw [left.lanes, right.lanes, h.params]
+ · rw [left.secret, right.secret]; exact h.words 32 (by simp)
+ · rw [left.secretLength, right.secretLength]; exact h.words 40 (by simp)
+ · rw [left.ad, right.ad]; exact h.words 48 (by simp)
+ · rw [left.adLength, right.adLength]; exact h.words 56 (by simp)
+ · rw [left.tagLength, right.tagLength, h.params]
+
+def abiReferences (s : State) : List Nat := Spec.Argon2.references (abiParams s)
+ (Spec.Blake2.bytesAt s.mem (s.gpr .rsi) (s.gpr .rdx).toNat)
+ (Spec.Blake2.bytesAt s.mem (s.gpr .rcx) (s.gpr .r8).toNat)
+ (Spec.Blake2.bytesAt s.mem (abiWord s 32) (abiWord s 40).toNat)
+ (Spec.Blake2.bytesAt s.mem (abiWord s 48) (abiWord s 56).toNat)
+
+theorem private_references {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) :
+ InitialBody.references (abiParams s) t = abiReferences s := by
+ have words := private_words h prepared
+ have password := private_input_bytes h prepared (104, 96) (by decide)
+ have salt := private_input_bytes h prepared (88, 80) (by decide)
+ have secret := private_input_bytes h prepared (200, 208) (by decide)
+ have ad := private_input_bytes h prepared (216, 224) (by decide)
+ simp only [Initial.inputRegion] at password salt secret ad
+ rw [words.password, words.passwordLength] at password
+ rw [words.salt, words.saltLength] at salt
+ rw [words.secret, words.secretLength] at secret
+ rw [words.ad, words.adLength] at ad
+ unfold InitialBody.references abiReferences
+ change Spec.Argon2.references (abiParams s) (Initial.inputBytes t 104 96)
+ (Initial.inputBytes t 88 80) (Initial.inputBytes t 200 208) (Initial.inputBytes t 216 224) = _
+ rw [password, salt, secret, ad]
+
+theorem private_parameters_related {s₁ s₂ t₁ t₂ : State}
+ (left : AbiEnvironment s₁) (right : AbiEnvironment s₂) (h : AbiPublic s₁ s₂)
+ (prepared₁ : PrivatePrepared (prologueState s₁) t₁)
+ (prepared₂ : PrivatePrepared (prologueState s₂) t₂) :
+ ParametersRelated (abiParams s₁) t₁ t₂ := by
+ have same := h.params
+ have ready₁ := private_body_ready left prepared₁
+ have ready₂ := private_body_ready right prepared₂
+ rw [← same] at ready₂
+ have keeps₁ := dimension_frame t₁ (abiParams s₁)
+ have keeps₂ := dimension_frame t₂ (abiParams s₁)
+ have words₁ := (private_words left prepared₁).of_state keeps₁
+ have words₂ := (private_words right prepared₂).of_state keeps₂
+ have sp : t₁.gpr .rsp = t₂.gpr .rsp := by rw [prepared₁.sp, prepared₂.sp, prologue_sp, prologue_sp, h.sp]
+ have bp : t₁.gpr .rbp = t₂.gpr .rbp := by rw [prepared₁.bp, prepared₂.bp, prologue_sp, prologue_sp, h.sp]
+ have bx : t₁.gpr .rbx = t₂.gpr .rbx := by
+ rw [private_scratch left prepared₁, private_scratch right prepared₂]
+ exact h.words 80 (by simp)
+ refine ⟨private_parameters left prepared₁, same.symm ▸ private_parameters right prepared₂, ?_⟩
+ refine ⟨ready₁, ready₂, ?_, ?_, ?_, ?_, ?_⟩
+ · refine ⟨⟨ready₁.hashSpace, ready₁.inputs⟩,
+ ⟨ready₂.hashSpace, ready₂.inputs⟩, ?_, ?_, ?_, ?_⟩
+ · rw [keeps₁.bp, keeps₂.bp]; exact bp
+ · rw [keeps₁.bx, keeps₂.bx]; exact bx
+ · rw [keeps₁.sp, keeps₂.sp]; exact sp
+ · exact words₁.public_words h words₂
+ · exact words₁.matrix.trans ((h.words 64 (by simp)).trans words₂.matrix.symm)
+ · exact words₁.output.trans ((h.words 88 (by simp)).trans words₂.output.symm)
+ · exact words₁.work.trans ((h.words 80 (by simp)).trans words₂.work.symm)
+ · unfold InitialBody.references
+ simp only [keeps₁.inputBytes, keeps₂.inputBytes]
+ change InitialBody.references (abiParams s₁) t₁ = InitialBody.references (abiParams s₁) t₂
+ rw [private_references left prepared₁, same, private_references right prepared₂]
+ exact h.references
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrologue.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrologue.lean
new file mode 100644
index 000000000..d1052dd32
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePrologue.lean
@@ -0,0 +1,66 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveAbi
+import VerifiedGarbage.Proof.Argon2.X86_64.DerivePrivatePrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveSaved
+
+/-! Private frame permissions and caller argument values after the ABI prologue. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def prologueState (s : State) : State := frameStart s Impl.Argon2.X86_64.Derive.saved
+
+theorem prologue_sp (s : State) : (prologueState s).gpr .rsp = s.gpr .rsp - BitVec.ofNat 64 320 :=
+ frameStart_sp s _
+
+theorem frameStart_locals (s : State) (rs : List Reg) :
+ (⟨(frameStart s rs).gpr .rsp, 272⟩ : Region) ∈ (frameStart s rs).wr := by
+ induction rs generalizing s with
+ | nil => rw [frameStart, pushed_wr, pushed_rsp]; exact List.mem_cons_self ..
+ | cons r rs ih => exact ih (pushed [r] s)
+
+theorem prologue_locals (s : State) : Covers [⟨(prologueState s).gpr .rsp, 272⟩] (prologueState s).wr := by
+ intro p n ⟨region, member, contains⟩
+ simp only [List.mem_singleton] at member; subst region
+ exact ⟨_, frameStart_locals s _, contains⟩
+
+theorem prologue_source (s : State) (j : Nat) :
+ (prologueState s).gpr .rsp + BitVec.ofNat 64 (copySource j) =
+ s.gpr .rsp + BitVec.ofNat 64 (8 * (j + 1)) := by
+ rw [prologue_sp]
+ unfold copySource
+ rw [show 328 + 8 * j = 320 + 8 * (j + 1) by omega,
+ BitVec.ofNat_add, ← BitVec.add_assoc, BitVec.sub_add_cancel]
+
+theorem prologue_reads {s : State} (h : AbiEnvironment s) :
+ ∀ j < 12, InRegions ((prologueState s).rd ++ (prologueState s).wr)
+ ((prologueState s).gpr .rsp + BitVec.ofNat 64 (copySource j)) 8 := by
+ intro j hj
+ rw [prologue_source]
+ refine ⟨abiArguments s, List.mem_append_left _ ?_, ?_⟩
+ · change abiArguments s ∈ (frameStart s Impl.Argon2.X86_64.Derive.saved).rd
+ rw [frameStart_rd, h.rd]; exact List.mem_append_right _ (List.mem_singleton_self _)
+ · unfold abiArguments
+ rw [show 8 * (j + 1) = 8 + 8 * j by omega, BitVec.ofNat_add, ← BitVec.add_assoc]
+ exact Offset.contains_base _ (by omega) (by omega)
+
+theorem prologue_word {s : State} (h : AbiEnvironment s) (j : Nat) (hj : j < 12) :
+ (prologueState s).mem.readW ((prologueState s).gpr .rsp + BitVec.ofNat 64 (copySource j)) 64 =
+ abiWord s (8 * (j + 1)) := by
+ rw [prologue_source]
+ have frame := frameStart_frame s Impl.Argon2.X86_64.Derive.saved (by decide) (by
+ have space := h.stack; change 320 ≤ (s.gpr .rsp).toNat; omega)
+ apply frame.readW (r := abiArguments s) ?_ ?_ (by decide)
+ · unfold abiArguments
+ rw [show 8 * (j + 1) = 8 + 8 * j by omega, BitVec.ofNat_add, ← BitVec.add_assoc]
+ exact Offset.contains_base _ (by omega) (by omega)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ unfold abiArguments
+ exact Offset.disjoint_below _ (by decide)
+
+theorem prologue_prepare (s : State) (h : AbiEnvironment s) :
+ WP isa Impl.Argon2.X86_64.Derive.prepare (prologueState s) (PrivatePrepared (prologueState s)) :=
+ private_prepare_ok _ (prologue_locals s) (prologue_reads h)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePublic.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePublic.lean
new file mode 100644
index 000000000..7f309166c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DerivePublic.lean
@@ -0,0 +1,42 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveAbi
+
+/-! The public entry-point relation reads u32 arguments at their declared width. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure AbiPublic (s t : State) : Prop where
+ sp : s.gpr .rsp = t.gpr .rsp
+ regs : ∀ r ∈ [.rsi, .rdx, .rcx, .r8], s.gpr r = t.gpr r
+ smallRegs : ∀ r ∈ [.rdi, .r9], (s.gpr r).setWidth 32 = (t.gpr r).setWidth 32
+ smallWords : ∀ d ∈ [8, 16, 24], (abiWord s d).setWidth 32 = (abiWord t d).setWidth 32
+ words : ∀ d ∈ [32, 40, 48, 56, 64, 72, 80, 88, 96], abiWord s d = abiWord t d
+ references : Spec.Argon2.references (abiParams s)
+ (Spec.Blake2.bytesAt s.mem (s.gpr .rsi) (s.gpr .rdx).toNat)
+ (Spec.Blake2.bytesAt s.mem (s.gpr .rcx) (s.gpr .r8).toNat)
+ (Spec.Blake2.bytesAt s.mem (abiWord s 32) (abiWord s 40).toNat)
+ (Spec.Blake2.bytesAt s.mem (abiWord s 48) (abiWord s 56).toNat) =
+ Spec.Argon2.references (abiParams t)
+ (Spec.Blake2.bytesAt t.mem (t.gpr .rsi) (t.gpr .rdx).toNat)
+ (Spec.Blake2.bytesAt t.mem (t.gpr .rcx) (t.gpr .r8).toNat)
+ (Spec.Blake2.bytesAt t.mem (abiWord t 32) (abiWord t 40).toNat)
+ (Spec.Blake2.bytesAt t.mem (abiWord t 48) (abiWord t 56).toNat)
+
+theorem abi_public (s t : State) (h : (Spec.Argon2.deriveContract X86_64.abi 344).pub s t) :
+ AbiPublic s t := by
+ sig_pub [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop] at h
+ sig_split h
+ constructor
+ all_goals sig_eval [abiWord, abiParams]
+ all_goals sig_and_intros
+ all_goals sig_close
+ all_goals with_reducible assumption
+
+theorem AbiPublic.params {s t : State} (h : AbiPublic s t) : abiParams s = abiParams t := by
+ unfold abiParams
+ rw [h.smallRegs .rdi (by simp), h.smallRegs .r9 (by simp),
+ h.smallWords 8 (by simp), h.smallWords 16 (by simp), h.words 96 (by simp)]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRegions.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRegions.lean
new file mode 100644
index 000000000..ab36f46f2
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRegions.lean
@@ -0,0 +1,62 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveMetadata
+
+/-! The private frame and called functions stay within the reviewed stack allowance. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem frameStart_wr_member (s : State) (rs : List Reg) (region : Region) (member : region ∈ s.wr) :
+ region ∈ (frameStart s rs).wr := by
+ induction rs generalizing s with
+ | nil => rw [frameStart, pushed_wr]; exact List.mem_cons_of_mem _ member
+ | cons r rs ih => apply ih; rw [pushed_wr]; exact List.mem_cons_of_mem _ member
+
+theorem private_wr_member {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (region : Region) (member : region ∈ s.wr) : region ∈ t.wr := by
+ rw [prepared.wr]
+ exact frameStart_wr_member s _ region member
+
+theorem private_bp {s t : State} (prepared : PrivatePrepared (prologueState s) t) :
+ t.gpr .rbp = s.gpr .rsp - BitVec.ofNat 64 320 := prepared.bp.trans (prologue_sp s)
+
+theorem private_sp {s t : State} (prepared : PrivatePrepared (prologueState s) t) :
+ t.gpr .rsp = s.gpr .rsp - BitVec.ofNat 64 320 := prepared.sp.trans (prologue_sp s)
+
+theorem private_frame_sub {s t : State} (prepared : PrivatePrepared (prologueState s) t) :
+ Region.Sub ⟨t.gpr .rbp, 272⟩ (below (s.gpr .rsp) 344) := by
+ rw [private_bp prepared]
+ exact Offset.sub_below _ (by decide) (by decide)
+
+theorem private_stack_sub {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (n : Nat) (bound : n ≤ 24) : Region.Sub (below (t.gpr .rsp) n) (below (s.gpr .rsp) 344) := by
+ rw [private_sp prepared]
+ unfold below
+ rw [BitVec.sub_sub, ← BitVec.ofNat_add]
+ exact Offset.sub_below _ (by omega) (by omega)
+
+theorem private_frame_disjoint {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (buffer : Region × Bool)
+ (member : buffer ∈ abiBuffers s ++ [(abiArguments s, false)]) :
+ (⟨t.gpr .rbp, 272⟩ : Region).Disjoint buffer.1 :=
+ (h.reserved _ (List.mem_cons_of_mem _ (List.mem_singleton_self _)) buffer member).sub_left
+ (private_frame_sub prepared)
+
+theorem private_stack_disjoint {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) (buffer : Region × Bool)
+ (member : buffer ∈ abiBuffers s ++ [(abiArguments s, false)]) (n : Nat) (bound : n ≤ 24) :
+ (below (t.gpr .rsp) n).Disjoint buffer.1 :=
+ (h.reserved _ (List.mem_cons_of_mem _ (List.mem_singleton_self _)) buffer member).sub_left
+ (private_stack_sub prepared n bound)
+
+theorem private_frame_stack {s t : State} (prepared : PrivatePrepared (prologueState s) t)
+ (n : Nat) (bound : n ≤ 24) : (⟨t.gpr .rbp, 272⟩ : Region).Disjoint (below (t.gpr .rsp) n) := by
+ rw [prepared.bp, prepared.sp]
+ exact Offset.base_disjoint_below _ (by omega)
+
+theorem private_work_member {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : abiWork s ∈ t.wr := by
+ apply private_wr_member prepared
+ rw [h.wr]; exact List.mem_cons_of_mem _ (List.mem_cons_self ..)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRestore.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRestore.lean
new file mode 100644
index 000000000..3451fb86c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveRestore.lean
@@ -0,0 +1,72 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveSaved
+import VerifiedGarbage.Proof.Framework.X86_64.RegUpd
+
+/-! Reload all saved registers from their unchanged stack slots. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem frameEnd_sp (s : State) (rs : List Reg) :
+ (frameEnd s rs).gpr .rsp = s.gpr .rsp + BitVec.ofNat 64 (272 + 8 * rs.length) := by
+ induction rs with
+ | nil => rw [frameEnd, popped_rsp]; rfl
+ | cons r rs ih =>
+ rw [frameEnd, popped_rsp, ih, BitVec.add_assoc,
+ ← BitVec.ofNat_add]
+ exact congrArg (fun n => s.gpr .rsp + BitVec.ofNat 64 n)
+ (by simp only [List.length_cons]; omega)
+
+theorem popped_one_reg (s : State) (r : Reg) (notSp : r ≠ .rsp) :
+ (popped r 1 s).gpr r = s.mem.readW (s.gpr .rsp) 64 := by
+ change ((s.setReg r (s.mem.readW (s.gpr .rsp) 64)).setReg .rsp (s.gpr .rsp + 8)).gpr r = _
+ rw [RegUpd.gpr_setReg_of_ne _ _ notSp, RegUpd.gpr_setReg_self]
+
+theorem frameEnd_restore (s : State) (rs : List Reg) (values : Reg → Addr)
+ (notSp : .rsp ∉ rs) (distinct : rs.Nodup)
+ (words : ∀ j (hj : j < rs.length),
+ s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (272 + 8 * rs.length - 8 * (j + 1))) 64 = values rs[j]) :
+ ∀ r ∈ rs, (frameEnd s rs).gpr r = values r := by
+ induction rs with
+ | nil => intro r hr; exact False.elim (List.not_mem_nil hr)
+ | cons r rs ih =>
+ simp only [List.mem_cons, not_or] at notSp
+ have nodup := List.nodup_cons.mp distinct
+ have innerWords : ∀ j (hj : j < rs.length),
+ s.mem.readW (s.gpr .rsp + BitVec.ofNat 64 (272 + 8 * rs.length - 8 * (j + 1))) 64 = values rs[j] := by
+ intro j hj
+ have word := words (j + 1) (by simp only [List.length_cons]; omega)
+ have offset : 272 + 8 * (r :: rs).length - 8 * (j + 1 + 1) =
+ 272 + 8 * rs.length - 8 * (j + 1) := by
+ simp only [List.length_cons]; omega
+ rw [offset] at word
+ exact word
+ have inner := ih notSp.2 nodup.2 innerWords
+ intro x hx
+ simp only [List.mem_cons] at hx
+ rcases hx with rfl | hx
+ · rw [frameEnd, popped_one_reg _ _ (Ne.symm notSp.1), frameEnd_mem, frameEnd_sp]
+ have word := words 0 (by simp)
+ have offset : 272 + 8 * (x :: rs).length - 8 * (0 + 1) = 272 + 8 * rs.length := by
+ simp only [List.length_cons]; omega
+ rw [offset] at word
+ exact word
+ · rw [frameEnd, popped_gpr r 1 (frameEnd s rs) (r' := x) (fun h => notSp.2 (h ▸ hx))
+ (fun h => nodup.1 (h ▸ hx))]
+ exact inner x hx
+
+theorem frame_restored (s t : State) (rs : List Reg) (notSp : .rsp ∉ rs)
+ (distinct : rs.Nodup) (space : 272 + 8 * rs.length ≤ (s.gpr .rsp).toNat)
+ (sp : t.gpr .rsp = (frameStart s rs).gpr .rsp)
+ (unchanged : ∀ j (_hj : j < rs.length),
+ t.mem.readW (s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1))) 64 =
+ (frameStart s rs).mem.readW (s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1))) 64) :
+ ∀ r ∈ rs, (frameEnd t rs).gpr r = s.gpr r := by
+ apply frameEnd_restore t rs s.gpr notSp distinct
+ intro j hj
+ have offsetBound : 8 * (j + 1) ≤ 272 + 8 * rs.length := by omega
+ rw [sp, frameStart_sp, ← Offset.ofNat_sub_ofNat offsetBound, Offset.sub_add_sub_cancel,
+ unchanged j hj]
+ exact frameStart_word s rs notSp space j hj
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveReturn.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveReturn.lean
new file mode 100644
index 000000000..d85b750ca
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveReturn.lean
@@ -0,0 +1,68 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveBodySaved
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveRestore
+
+/-! The nested ABI frames return the complete result and restore all saved registers. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def wholeWrites (s : State) : List Region := [abiMatrix s, abiWork s, abiOutput s, below (s.gpr .rsp) 344]
+
+theorem BodyDone.whole_frame {s t : State} (h : AbiEnvironment s) (done : BodyDone s t) :
+ Frame (wholeWrites s) s.mem t.mem := by
+ have prologue := frameStart_frame s Impl.Argon2.X86_64.Derive.saved (by decide) (by
+ have space := h.stack; change 320 ≤ (s.gpr .rsp).toNat; omega)
+ apply (prologue.sub ?_).trans (done.frame.sub ?_)
+ · intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ exact ⟨below (s.gpr .rsp) 344, by simp [wholeWrites], below_sub (by decide) (by decide)⟩
+ · intro r hr
+ simp only [bodyWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact ⟨abiMatrix s, by simp [wholeWrites], fun _ h => h⟩
+ · exact ⟨abiWork s, by simp [wholeWrites], fun _ h => h⟩
+ · exact ⟨abiOutput s, by simp [wholeWrites], fun _ h => h⟩
+ · refine ⟨below (s.gpr .rsp) 344, by simp [wholeWrites], ?_⟩
+ rw [prologue_sp]
+ exact Offset.sub_below _ (by decide) (by decide)
+ · refine ⟨below (s.gpr .rsp) 344, by simp [wholeWrites], ?_⟩
+ rw [prologue_sp]
+ unfold below
+ rw [BitVec.sub_sub, ← BitVec.ofNat_add]
+ exact Region.sub_prefix (by decide)
+
+theorem return_post {s t : State} (done : BodyDone s t) :
+ (Spec.Argon2.deriveContract X86_64.abi 344).post s (frameEnd t Impl.Argon2.X86_64.Derive.saved) := by
+ have post := done.post
+ sig_post [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop] at post
+ sig_post [Spec.Argon2.deriveContract, Spec.Argon2.deriveSig, X86_64.abi,
+ X86_64.argRegs, X86_64.stackArg, X86_64.stackArgAddr, List.range, List.range.loop]
+ exact post
+
+theorem code_wp (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State)
+ (pre : (Spec.Argon2.deriveContract X86_64.abi 344).pre s) :
+ WP isa (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v)) s fun t =>
+ (Spec.Argon2.deriveContract X86_64.abi 344).post s t ∧
+ (∀ r ∈ calleeSaved, t.gpr r = s.gpr r) ∧ Frame (wholeWrites s) s.mem t.mem := by
+ have h := abi_environment s pre
+ unfold Impl.Argon2.X86_64.Derive.code
+ apply frame_ok s Impl.Argon2.X86_64.Derive.saved _ _ (by decide)
+ (by have space := h.stack; change 320 ≤ (s.gpr .rsp).toNat; omega)
+ refine (body_ok v name s h).mono ?_
+ intro t done
+ refine ⟨done.sp, done.wr, return_post done, ?_, ?_⟩
+ · have restored := frame_restored s t Impl.Argon2.X86_64.Derive.saved (by decide) (by decide)
+ (by have space := h.stack; change 320 ≤ (s.gpr .rsp).toNat; omega) done.sp
+ (fun j hj => done.saved h j hj)
+ have stack := (frameEnd_metadata s t Impl.Argon2.X86_64.Derive.saved done.sp done.wr).1
+ intro r hr
+ have member : ∀ r ∈ calleeSaved, r = .rsp ∨ r ∈ Impl.Argon2.X86_64.Derive.saved := by decide
+ rcases member r hr with rfl | hr
+ · exact stack
+ · exact restored r hr
+ · rw [frameEnd_mem]
+ exact done.whole_frame h
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSaved.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSaved.lean
new file mode 100644
index 000000000..82cb6a8c8
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSaved.lean
@@ -0,0 +1,46 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveFrameState
+
+/-! The nested prologue stores every callee-saved register at its exact ABI slot. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem frameStart_word (s : State) (rs : List Reg) (notSp : .rsp ∉ rs)
+ (space : 272 + 8 * rs.length ≤ (s.gpr .rsp).toNat) (j : Nat) (bound : j < rs.length) :
+ (frameStart s rs).mem.readW (s.gpr .rsp - BitVec.ofNat 64 (8 * (j + 1))) 64 = s.gpr rs[j] := by
+ induction rs generalizing s j with
+ | nil => exact absurd bound (Nat.not_lt_zero _)
+ | cons r rs ih =>
+ simp only [List.mem_cons, not_or] at notSp
+ have enough : 8 ≤ (s.gpr .rsp).toNat := by simp only [List.length_cons] at space; omega
+ have innerSpace : 272 + 8 * rs.length ≤ ((pushed [r] s).gpr .rsp).toNat := by
+ rw [pushed_rsp]
+ simp only [List.length_singleton, Nat.mul_one]
+ rw [toNat_sub_ofNat enough]
+ simp only [List.length_cons] at space; omega
+ cases j with
+ | zero =>
+ have stored := (pushRegs_mem s [r] (by simpa using notSp.1)
+ (by simpa using enough)).2 0 (by simp)
+ have inner := frameStart_frame (pushed [r] s) rs notSp.2 innerSpace
+ have unchanged : (frameStart (pushed [r] s) rs).mem.readW ((pushed [r] s).gpr .rsp) 64 =
+ (pushed [r] s).mem.readW ((pushed [r] s).gpr .rsp) 64 := inner.readW
+ (r := ⟨(pushed [r] s).gpr .rsp, 8⟩) (Region.contains_self _ _) (by
+ intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ apply Offset.base_disjoint_below
+ have limit := (s.gpr .rsp).isLt
+ simp only [List.length_cons] at space; omega) (by decide)
+ rw [pushed_rsp] at unchanged
+ simp only [List.length_singleton, Nat.mul_one] at unchanged
+ exact unchanged.trans stored
+ | succ j =>
+ have word := ih (pushed [r] s) notSp.2 innerSpace j (by simpa using bound)
+ rw [pushed_rsp, pushed_gpr _ _ (fun h => notSp.2 (h ▸ List.getElem_mem _))] at word
+ simp only [List.length_singleton, Nat.mul_one] at word
+ rw [BitVec.sub_sub, ← BitVec.ofNat_add,
+ show 8 + 8 * (j + 1) = 8 * (j + 1 + 1) by omega] at word
+ exact word
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveScratch.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveScratch.lean
new file mode 100644
index 000000000..4f6f02886
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveScratch.lean
@@ -0,0 +1,29 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveStore
+
+/-! Load the caller-supplied hash workspace after saving incoming arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure ScratchLoaded (s t : State) : Prop where
+ scratch : t.gpr .rbx = s.mem.readW (s.gpr .rbp + 248) 64
+ regs : ∀ r, r ≠ .rbx → t.gpr r = s.gpr r
+ mem : t.mem = s.mem
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem scratch_ok (s : State) (read : InRegions (s.rd ++ s.wr) (s.gpr .rbp + 248) 8) :
+ WP isa (.block [.mov .rbx (.mem (Impl.Argon2.X86_64.at_ .rbp 248))]) s (ScratchLoaded s) := by
+ have ea : s.ea (Impl.Argon2.X86_64.at_ .rbp 248) = s.gpr .rbp + 248 := rfl
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea, read,
+ ite_true, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, ?_, rfl, rfl, rfl, rfl⟩
+ · exact RegUpd.gpr_setReg_self ..
+ · intro r hr
+ exact RegUpd.gpr_setReg_of_ne _ _ hr
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSeparation.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSeparation.lean
new file mode 100644
index 000000000..838141811
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSeparation.lean
@@ -0,0 +1,40 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveRegions
+
+/-! Buffer separation is supplied by the shared signature, including read-only arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure AbiSeparation (s : State) : Prop where
+ inputWork : ∀ r ∈ abiInputs s, r.Disjoint (abiWork s)
+ matrixWork : (abiMatrix s).Disjoint (abiWork s)
+ outputWork : (abiOutput s).Disjoint (abiWork s)
+ matrixOutput : (abiMatrix s).Disjoint (abiOutput s)
+
+theorem abi_separation {s : State} (h : AbiEnvironment s) : AbiSeparation s := by
+ have pairs := h.pairs
+ sig_eval [abiBuffers, abiInputs, abiMatrix, abiWork, abiOutput, abiArguments] at pairs
+ sig_split pairs
+ constructor
+ all_goals sig_eval [abiInputs, abiMatrix, abiWork, abiOutput]
+ all_goals sig_and_intros
+ all_goals first
+ | with_reducible assumption
+ | with_reducible exact Region.Disjoint.symm ‹_›
+
+theorem private_scratch {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : t.gpr .rbx = (abiWork s).base := by
+ have word := prologue_word h 9 (by decide)
+ change (prologueState s).mem.readW ((prologueState s).gpr .rsp + 400) 64 = abiWord s 80 at word
+ exact prepared.scratch.trans word
+
+theorem abi_input_lengths {s : State} (h : AbiEnvironment s) : ∀ r ∈ abiInputs s, r.len < 2 ^ 32 := by
+ have valid := h.valid
+ unfold Spec.Argon2.valid at valid
+ obtain ⟨_, _, _, _, _, _, _, _, password, salt, secret, ad⟩ := valid
+ sig_eval [abiInputs]
+ sig_and_intros
+ all_goals with_reducible assumption
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSetup.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSetup.lean
new file mode 100644
index 000000000..aa59b63ff
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSetup.lean
@@ -0,0 +1,87 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveEntry
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveStores
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveScratch
+import VerifiedGarbage.TCB.X86_64.Target
+import VerifiedGarbage.Proof.Framework.X86_64.Inline
+
+/-! Save the register arguments into the local derivation frame. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def arguments : List (Nat × Reg) :=
+ [(72, .r9), (80, .r8), (88, .rcx), (96, .rdx), (104, .rsi), (112, .rdi)]
+
+def argumentValue (s : State) (r : Reg) : Addr :=
+ if r = .rdi ∨ r = .r9 then ((s.gpr r).setWidth 32).setWidth 64 else s.gpr r
+
+theorem Entered.argument {s t : State} (h : Entered s t) (r : Reg) (bp : r ≠ .rbp) :
+ t.gpr r = argumentValue s r := by
+ unfold argumentValue
+ by_cases di : r = .rdi
+ · subst r; rw [ite_eq_left (Or.inl rfl)]; exact h.kind
+ · by_cases nine : r = .r9
+ · subst r; rw [ite_eq_left (Or.inr rfl)]; exact h.passes
+ · rw [ite_eq_right (by simp only [di, nine, or_self, not_false_eq_true])]
+ exact h.regs r bp di nine
+
+structure SetupDone (s t : State) : Prop where
+ bp : t.gpr .rbp = s.gpr .rsp
+ sp : t.gpr .rsp = s.gpr .rsp
+ scratch : t.gpr .rbx = s.mem.readW (s.gpr .rsp + 248) 64
+ values : ∀ arg ∈ arguments, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 arg.1) 64 = argumentValue s arg.2
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbp → r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ frame : Frame [⟨s.gpr .rsp, 120⟩] s.mem t.mem
+
+theorem setup_ok (s : State) (frameWrite : Covers [⟨s.gpr .rsp, 120⟩] s.wr)
+ (read : InRegions (s.rd ++ s.wr) (s.gpr .rsp + 248) 8) :
+ WP isa (.block Impl.Argon2.X86_64.Derive.setup) s (SetupDone s) := by
+ change WP isa (.block (([.mov .rbp (.reg .rsp), .mov32 .rdi (.reg .rdi), .mov32 .r9 (.reg .r9)] : List Instr) ++
+ (arguments.map fun arg => .store (Impl.Argon2.X86_64.at_ .rbp arg.1) arg.2) ++
+ ([.mov .rbx (.mem (Impl.Argon2.X86_64.at_ .rbp 248))] : List Instr))) s _
+ rw [List.append_assoc, WP.block_append_iff]
+ refine (entry_ok s).mono ?_
+ intro a entered
+ rw [WP.block_append_iff]
+ have write : ∀ arg ∈ arguments, InRegions a.wr (a.gpr .rbp + BitVec.ofNat 64 arg.1) 8 := by
+ intro arg ha
+ rw [entered.wr, entered.bp]
+ apply frameWrite
+ have bounds : ∀ arg ∈ arguments, arg.1 + 8 ≤ 120 := by decide
+ exact ⟨_, List.mem_singleton_self _, Offset.contains_base _ (bounds arg ha) (by have := bounds arg ha; omega)⟩
+ refine (stores_values_ok arguments a write (by decide) (by decide)).mono ?_
+ rintro b ⟨saved, values⟩
+ have bp : b.gpr .rbp = s.gpr .rsp := by rw [saved.regs, entered.bp]
+ have scratchWord : b.mem.readW (b.gpr .rbp + 248) 64 = s.mem.readW (s.gpr .rsp + 248) 64 := by
+ have kept := saved.other_word 248 (by decide) (by decide) (by decide)
+ change b.mem.readW (b.gpr .rbp + 248) 64 = a.mem.readW (a.gpr .rbp + 248) 64 at kept
+ rw [kept, entered.bp, entered.mem]
+ refine (scratch_ok b (by rw [saved.rd, saved.wr, bp, entered.rd, entered.wr]; exact read)).mono ?_
+ intro t loaded
+ refine ⟨(loaded.regs .rbp (by decide)).trans bp, ?_, loaded.scratch.trans scratchWord, ?_, ?_,
+ loaded.rd.trans (saved.rd.trans entered.rd), loaded.wr.trans (saved.wr.trans entered.wr),
+ loaded.mxcsr.trans (saved.mxcsr.trans entered.mxcsr), ?_⟩
+ · rw [loaded.regs .rsp (by decide), saved.regs]
+ exact entered.regs .rsp (by decide) (by decide) (by decide)
+ · intro arg ha
+ rw [loaded.mem, loaded.regs .rbp (by decide), values arg ha]
+ have notBp : ∀ arg ∈ arguments, arg.2 ≠ .rbp := by decide
+ exact entered.argument arg.2 (notBp arg ha)
+ · intro r hr hb hx
+ have other : ∀ r ∈ calleeSaved, r ≠ .rdi ∧ r ≠ .r9 := by decide
+ rw [loaded.regs r hx, saved.regs]
+ exact entered.regs r hb (other r hr).1 (other r hr).2
+ · rw [loaded.mem, ← entered.mem]
+ have frame := saved.frame
+ rw [entered.bp] at frame
+ apply frame.sub
+ intro region hr
+ obtain ⟨arg, ha, rfl⟩ := List.mem_map.mp hr
+ have bounds : ∀ arg ∈ arguments, arg.1 + 8 ≤ 120 := by decide
+ exact ⟨_, List.mem_singleton_self _, Offset.sub_base _ (bounds arg ha)⟩
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSpSafe.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSpSafe.lean
new file mode 100644
index 000000000..344c4bd21
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveSpSafe.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveLit
+import VerifiedGarbage.Proof.Argon2.X86_64.HPrime.Verified
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialLit
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitLit
+import VerifiedGarbage.Proof.Argon2.X86_64.ParametersLit
+
+/-! The complete derivation does not directly write the stack pointer for every BLAKE2b backend. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+local notation "property" => (fun i => !isa.writesSp i)
+
+theorem initial_spSafe (v : Proof.Blake2.X86_64.Backend) :
+ (Impl.Argon2.X86_64.Initial.code (HPrime.hash v)).all property = true := by
+ have init : (HPrime.hash v).init.all property = true := by
+ change (Impl.Blake2.X86_64.Stream.init Spec.Blake2.b).all _ = true
+ lit_decide
+ have update : (HPrime.hash v).update.all property = true := v.updateSpSafe
+ have finalize : (HPrime.hash v).finalize.all property = true := v.finalizeSpSafe
+ simp only [Impl.Argon2.X86_64.Initial.code, Impl.Argon2.X86_64.Initial.start,
+ Impl.Argon2.X86_64.Initial.absorb, Impl.Argon2.X86_64.Initial.finish,
+ Impl.Argon2.X86_64.HPrime.init, Impl.Argon2.X86_64.HPrime.absorbFixed,
+ Impl.Argon2.X86_64.HPrime.update, Impl.Argon2.X86_64.HPrime.finalize, Code.all]
+ rw [init, update, finalize]
+ lit_decide
+
+theorem memory_spSafe (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ (Impl.Argon2.X86_64.MemoryInit.code name (HPrime.hash v)).all property = true := by
+ simp only [Impl.Argon2.X86_64.MemoryInit.code, Impl.Argon2.X86_64.MemoryInit.clear,
+ Impl.Argon2.X86_64.MemoryInit.lane, Impl.Argon2.X86_64.MemoryInit.block, Code.all]
+ rw [HPrime.spSafe v]
+ lit_decide
+
+theorem frame_spSafe (body : Prog isa) (rs : List Reg) (h : body.all property = true)
+ (safe : ∀ r ∈ rs, r ≠ .rsp) :
+ (Impl.Argon2.X86_64.Derive.frame body rs).all property = true := by
+ induction rs with
+ | nil =>
+ change (true && body.all property && true) = true
+ rw [h]; rfl
+ | cons r rs ih =>
+ change (true && (Impl.Argon2.X86_64.Derive.frame body rs).all property && property (.pop r 1)) = true
+ rw [ih (fun q hq => safe q (List.mem_cons_of_mem _ hq))]
+ simp only [Bool.true_and]
+ change Bool.not ((some r == some Reg.rsp) : Bool) = true
+ rw [Bool.not_eq_true', beq_eq_false_iff_ne]
+ exact fun eq => safe r (List.mem_cons_self ..) (Option.some.inj eq)
+
+theorem code_spSafe (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v)).all property = true := by
+ unfold Impl.Argon2.X86_64.Derive.code
+ apply frame_spSafe (safe := by decide)
+ simp only [Impl.Argon2.X86_64.Derive.body, Impl.Argon2.X86_64.InitialBody.code,
+ Impl.Argon2.X86_64.InitFill.code, Impl.Argon2.X86_64.FillFinish.code,
+ Impl.Argon2.X86_64.Finish.code, Impl.Argon2.X86_64.FinalOutput.code, Code.all]
+ rw [initial_spSafe v, memory_spSafe v name, HPrime.spSafe v]
+ lit_decide
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStore.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStore.lean
new file mode 100644
index 000000000..cd9a91675
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStore.lean
@@ -0,0 +1,40 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveNormalize
+
+/-! Save each incoming argument with one short symbolic execution. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+structure Stored (s t : State) (d : Nat) (r : Reg) : Prop where
+ mem : t.mem = s.mem.writeW (s.gpr .rbp + BitVec.ofNat 64 d) (s.gpr r)
+ regs : t.gpr = s.gpr
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem store_ok (s : State) (d : Nat) (r : Reg)
+ (write : InRegions s.wr (s.gpr .rbp + BitVec.ofNat 64 d) 8) :
+ WP isa (.block [.store (Impl.Argon2.X86_64.at_ .rbp d) r]) s (Stored s · d r) := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, State.store64,
+ State.ea, Impl.Argon2.X86_64.at_, BitVec.ofInt_natCast, write,
+ ite_true, Option.some.injEq, exists_eq_left']
+ exact ⟨rfl, rfl, rfl, rfl, rfl⟩
+
+theorem Stored.word {s t : State} {d : Nat} {r : Reg} (h : Stored s t d r) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 = s.gpr r := by
+ rw [h.regs, h.mem, Mem.readW_writeW_self64]
+
+theorem Stored.frame {s t : State} {d : Nat} {r : Reg} (h : Stored s t d r) :
+ Frame [⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩] s.mem t.mem := by
+ rw [h.mem]
+ exact (Frame.refl _ _).writeW (List.mem_singleton_self _) _ (Region.contains_self _ _)
+
+theorem Stored.other_word {s t : State} {d : Nat} {r : Reg} (h : Stored s t d r)
+ (e : Nat) (separate : e + 8 ≤ d ∨ d + 8 ≤ e) (ed : e + 8 ≤ 2 ^ 64) (dd : d + 8 ≤ 2 ^ 64) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 e) 64 = s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 e) 64 := by
+ rw [h.regs, h.mem]
+ exact Mem.readW_writeW_sep (Offset.sep _ separate ed dd) (by decide)
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStores.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStores.lean
new file mode 100644
index 000000000..ec49efc27
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveStores.lean
@@ -0,0 +1,92 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveStore
+
+/-! Compose argument stores without re-executing a growing symbolic memory state. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+def saveMemory (s : State) (args : List (Nat × Reg)) : Mem :=
+ args.foldl (fun m arg => m.writeW (s.gpr .rbp + BitVec.ofNat 64 arg.1) (s.gpr arg.2)) s.mem
+
+structure Saved (s t : State) (args : List (Nat × Reg)) : Prop where
+ mem : t.mem = saveMemory s args
+ regs : t.gpr = s.gpr
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ frame : Frame (args.map fun arg => (⟨s.gpr .rbp + BitVec.ofNat 64 arg.1, 8⟩ : Region)) s.mem t.mem
+
+theorem stores_ok (args : List (Nat × Reg)) (s : State)
+ (write : ∀ arg ∈ args, InRegions s.wr (s.gpr .rbp + BitVec.ofNat 64 arg.1) 8) :
+ WP isa (.block (args.map fun arg => .store (Impl.Argon2.X86_64.at_ .rbp arg.1) arg.2)) s (Saved s · args) := by
+ induction args generalizing s with
+ | nil => exact WP.block_nil ⟨rfl, rfl, rfl, rfl, rfl, Frame.refl _ _⟩
+ | cons arg args ih =>
+ rw [List.map_cons]
+ change WP isa (.block (([.store (Impl.Argon2.X86_64.at_ .rbp arg.1) arg.2] : List Instr) ++ _)) s _
+ rw [WP.block_append_iff]
+ refine (store_ok s arg.1 arg.2 (write arg (List.mem_cons_self ..))).mono ?_
+ intro t ht
+ refine (ih t (fun a ha => by rw [ht.wr, ht.regs]; exact write a (List.mem_cons_of_mem arg ha))).mono ?_
+ intro u hu
+ refine ⟨?_, hu.regs.trans ht.regs, hu.rd.trans ht.rd, hu.wr.trans ht.wr, hu.mxcsr.trans ht.mxcsr, ?_⟩
+ · rw [hu.mem]
+ unfold saveMemory
+ rw [ht.regs, ht.mem, List.foldl_cons]
+ · apply (ht.frame.mono ?_).trans
+ · have frame := hu.frame
+ rw [ht.regs] at frame
+ exact frame.mono (fun _ h => List.mem_cons_of_mem _ h)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ exact List.mem_cons_self ..
+
+theorem Saved.other_word {s t : State} {args : List (Nat × Reg)} (h : Saved s t args)
+ (e : Nat) (bound : e + 8 ≤ 2 ^ 64)
+ (separate : ∀ arg ∈ args, e + 8 ≤ arg.1 ∨ arg.1 + 8 ≤ e)
+ (bounds : ∀ arg ∈ args, arg.1 + 8 ≤ 2 ^ 64) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 e) 64 = s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 e) 64 := by
+ rw [h.regs]
+ apply h.frame.readW (r := ⟨s.gpr .rbp + BitVec.ofNat 64 e, 8⟩) (Region.contains_self _ _) ?_ (by decide)
+ intro region hr
+ obtain ⟨arg, member, rfl⟩ := List.mem_map.mp hr
+ exact Offset.disjoint _ (separate arg member) bound (bounds arg member)
+
+theorem stores_values_ok (args : List (Nat × Reg)) (s : State)
+ (write : ∀ arg ∈ args, InRegions s.wr (s.gpr .rbp + BitVec.ofNat 64 arg.1) 8)
+ (separate : args.Pairwise fun a b => a.1 + 8 ≤ b.1 ∨ b.1 + 8 ≤ a.1)
+ (bounds : ∀ arg ∈ args, arg.1 + 8 ≤ 2 ^ 64) :
+ WP isa (.block (args.map fun arg => .store (Impl.Argon2.X86_64.at_ .rbp arg.1) arg.2)) s fun t =>
+ Saved s t args ∧ ∀ arg ∈ args, t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 arg.1) 64 = s.gpr arg.2 := by
+ induction args generalizing s with
+ | nil => exact WP.block_nil ⟨⟨rfl, rfl, rfl, rfl, rfl, Frame.refl _ _⟩, by simp⟩
+ | cons arg args ih =>
+ obtain ⟨headSep, tailSep⟩ := List.pairwise_cons.mp separate
+ rw [List.map_cons]
+ change WP isa (.block (([.store (Impl.Argon2.X86_64.at_ .rbp arg.1) arg.2] : List Instr) ++ _)) s _
+ rw [WP.block_append_iff]
+ refine (store_ok s arg.1 arg.2 (write arg (List.mem_cons_self ..))).mono ?_
+ intro t ht
+ refine (ih t (fun a ha => by rw [ht.wr, ht.regs]; exact write a (List.mem_cons_of_mem arg ha))
+ tailSep (fun a ha => bounds a (List.mem_cons_of_mem arg ha))).mono ?_
+ rintro u ⟨hu, values⟩
+ have saved : Saved s u (arg :: args) := by
+ refine ⟨?_, hu.regs.trans ht.regs, hu.rd.trans ht.rd, hu.wr.trans ht.wr, hu.mxcsr.trans ht.mxcsr, ?_⟩
+ · rw [hu.mem]; unfold saveMemory; rw [ht.regs, ht.mem, List.foldl_cons]
+ · apply (ht.frame.mono ?_).trans
+ · have frame := hu.frame
+ rw [ht.regs] at frame
+ exact frame.mono (fun _ h => List.mem_cons_of_mem _ h)
+ · intro region hr
+ simp only [List.mem_singleton] at hr; subst region
+ exact List.mem_cons_self ..
+ refine ⟨saved, ?_⟩
+ intro a ha
+ rcases List.mem_cons.mp ha with rfl | ha
+ · rw [hu.other_word a.1 (bounds a (List.mem_cons_self ..)) headSep
+ (fun b hb => bounds b (List.mem_cons_of_mem a hb))]
+ exact ht.word
+ · rw [values a ha, ht.regs]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveVerified.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveVerified.lean
new file mode 100644
index 000000000..62fdb7e15
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveVerified.lean
@@ -0,0 +1,17 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveCorrect
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveCT
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveContract
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveSpSafe
+
+/-! Complete Argon2 verification against the reviewed shared API contract. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+
+theorem verified (v : Proof.Blake2.X86_64.Backend) (name : String) :
+ Verified X86_64.target (Impl.Argon2.X86_64.Derive.code name (HPrime.hash v))
+ (Spec.Argon2.deriveContract X86_64.abi 344) :=
+ ⟨code_correct v name, code_ct v name, contract_sat⟩
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveWords.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveWords.lean
new file mode 100644
index 000000000..61d0b5ed0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/DeriveWords.lean
@@ -0,0 +1,69 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.DeriveRegions
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBody
+
+/-! Exact words consumed by hashing, initialization, filling, and finalization. -/
+
+namespace VG.Proof.Argon2.X86_64.Derive
+
+open VG VG.X86_64
+open VG.Proof.Argon2.X86_64.Initial (wordAt)
+
+theorem params_variant_code (kind passes memory lanes tagLen : Nat) (bound : kind ≤ 2) :
+ (Spec.Argon2.params kind passes memory lanes tagLen).variant.code = kind := by
+ by_cases zero : kind = 0
+ · subst kind; rfl
+ · by_cases one : kind = 1
+ · subst kind; rfl
+ · have two : kind = 2 := by omega
+ subst kind; rfl
+
+structure DeriveWords (s t : State) : Prop where
+ passes : wordAt t 72 = BitVec.ofNat 64 (abiParams s).passes
+ saltLength : wordAt t 80 = s.gpr .r8
+ salt : wordAt t 88 = s.gpr .rcx
+ passwordLength : wordAt t 96 = s.gpr .rdx
+ password : wordAt t 104 = s.gpr .rsi
+ kind : wordAt t 112 = BitVec.ofNat 64 (abiParams s).variant.code
+ memory : wordAt t 176 = BitVec.ofNat 64 (abiParams s).memory
+ lanes : wordAt t 184 = BitVec.ofNat 64 (abiParams s).lanes
+ secret : wordAt t 200 = abiWord s 32
+ secretLength : wordAt t 208 = abiWord s 40
+ ad : wordAt t 216 = abiWord s 48
+ adLength : wordAt t 224 = abiWord s 56
+ matrix : wordAt t 232 = abiWord s 64
+ blocks : wordAt t 240 = BitVec.ofNat 64 (abiParams s).blocks
+ work : wordAt t 248 = abiWord s 80
+ output : wordAt t 256 = abiWord s 88
+ tagLength : wordAt t 264 = BitVec.ofNat 64 (abiParams s).tagLen
+
+theorem private_words {s t : State} (h : AbiEnvironment s)
+ (prepared : PrivatePrepared (prologueState s) t) : DeriveWords s t := by
+ have parameters := private_parameters h prepared
+ refine ⟨?_, private_argument_word prepared (80, .r8) (by decide),
+ private_argument_word prepared (88, .rcx) (by decide),
+ private_argument_word prepared (96, .rdx) (by decide),
+ private_argument_word prepared (104, .rsi) (by decide), ?_, parameters.memoryWord, parameters.lanesWord,
+ private_stack_word h prepared 3 (by decide), private_stack_word h prepared 4 (by decide),
+ private_stack_word h prepared 5 (by decide), private_stack_word h prepared 6 (by decide),
+ private_stack_word h prepared 7 (by decide), ?_, private_stack_word h prepared 9 (by decide),
+ private_stack_word h prepared 10 (by decide), ?_⟩
+ · have word := private_argument_word prepared (72, .r9) (by decide)
+ change wordAt t 72 = ((s.gpr .r9).setWidth 32).setWidth 64 at word
+ change wordAt t 72 = BitVec.ofNat 64 ((s.gpr .r9).setWidth 32).toNat
+ rw [word, BitVec.ofNat_toNat]
+ · have code := params_variant_code ((s.gpr .rdi).setWidth 32).toNat
+ (abiParams s).passes (abiParams s).memory (abiParams s).lanes (abiParams s).tagLen h.kind
+ change (abiParams s).variant.code = ((s.gpr .rdi).setWidth 32).toNat at code
+ rw [code]
+ have word := private_argument_word prepared (112, .rdi) (by decide)
+ change wordAt t 112 = ((s.gpr .rdi).setWidth 32).setWidth 64 at word
+ rw [word, BitVec.ofNat_toNat]
+ · have word := private_stack_word h prepared 8 (by decide)
+ change wordAt t 240 = abiWord s 72 at word
+ rw [word, ← h.blocks, BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ · have word := private_stack_word h prepared 11 (by decide)
+ change wordAt t 264 = abiWord s 96 at word
+ change wordAt t 264 = BitVec.ofNat 64 (abiWord s 96).toNat
+ rw [word, BitVec.ofNat_toNat, BitVec.setWidth_eq]
+
+end VG.Proof.Argon2.X86_64.Derive
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillAllocation.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillAllocation.lean
new file mode 100644
index 000000000..82224d0b9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillAllocation.lean
@@ -0,0 +1,25 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelStable
+
+/-! Transport the allocation using just its public pointers and permissions. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Layout.of_preserved {p : Params} {s t : State} (h : Layout p s)
+ (bp : t.gpr .rbp = s.gpr .rbp) (sp : t.gpr .rsp = s.gpr .rsp)
+ (base : matrix t = matrix s) (scratch : work t = work s)
+ (rd : t.rd = s.rd) (wr : t.wr = s.wr) : Layout p t := by
+ constructor
+ · rw [rd, wr, bp]; exact h.frameRead
+ · rw [wr, bp]; exact h.frameWrite
+ · rw [base, wr]; exact h.matrixWrite
+ · rw [scratch, wr]; exact h.workWrite
+ · rw [base, scratch]; exact h.matrixWork
+ · rw [base, bp]; exact h.matrixFrame
+ · rw [base, sp]; exact h.matrixStack
+ · rw [bp, scratch]; exact h.frameWork
+ · rw [bp, sp]; exact h.frameStack
+ · rw [sp, scratch]; exact h.stackWork
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlock.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlock.lean
new file mode 100644
index 000000000..dd5f699f0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlock.lean
@@ -0,0 +1,47 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlockFrame
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCacheInvariant
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelSpec
+
+/-! Complete active filling cell against the reviewed matrix transition. -/
+
+namespace VG.Proof.Argon2.X86_64.FillBlock
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Done (s t : State) (p : Params) (pass lane slice index : Nat) (state : FillState) : Prop where
+ ready : ∃ old, RandomSource.Ready p pass lane slice index old t
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks
+ (fillBlock p pass slice lane index state).memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem code_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : RandomSource.Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillBlock.code s (Done s · p pass lane slice index state) := by
+ unfold Impl.Argon2.X86_64.FillBlock.code
+ refine WP.seq ((RandomSource.code_ok s p pass lane slice index old h state represented).mono ?_)
+ intro a source
+ obtain ⟨counter, ready⟩ := source.ready
+ have baseA : FillKernel.matrix a = FillKernel.matrix s := source.frame_word h 232 (by decide) (by decide)
+ have workA : AddressCalls.work a = AddressCalls.work s := source.frame_word h 248 (by decide) (by decide)
+ refine (FillKernel.code_spec_ok a p pass lane slice index ready.filling state source.represented source.random).mono ?_
+ rintro t ⟨done, matrix⟩
+ have baseT : FillKernel.matrix t = FillKernel.matrix a := done.frame_word ready.filling 232 (by decide) (by decide)
+ have workT : AddressCalls.work t = AddressCalls.work a := done.frame_word ready.filling 248 (by decide) (by decide)
+ refine ⟨⟨counter, ready.after_fill done⟩, ?_, baseT.trans baseA, workT.trans workA, ?_,
+ done.rd.trans source.rd, done.wr.trans source.wr, ?_, done.mxcsr.trans source.mxcsr⟩
+ · rw [baseT]; exact matrix
+ · intro r hr; exact (done.regs r hr).trans (source.regs r hr)
+ · have frame := kernel_frame ready.filling done
+ rw [writes, baseA, workA, source.regs .rsp (by simp [calleeSaved]),
+ source.regs .rbp (by simp [calleeSaved])] at frame
+ exact (source_frame source).trans frame
+
+end VG.Proof.Argon2.X86_64.FillBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCT.lean
new file mode 100644
index 000000000..cf4a81680
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCT.lean
@@ -0,0 +1,72 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourceCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelCT
+
+/-! An active filling cell leaks only its specified data-dependent reference. -/
+
+namespace VG.Proof.Argon2.X86_64.FillBlock
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass lane slice index old : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ source : RandomSource.Related p pass lane slice index old s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ references : independent p pass slice = false →
+ reference p pass lane slice index (Proof.Argon2.FillStep.random p pass lane slice index leftState.memory) =
+ reference p pass lane slice index (Proof.Argon2.FillStep.random p pass lane slice index rightState.memory)
+
+theorem Related.of_indices {p : Params} {pass lane slice index old : Nat} {s t : State}
+ {leftState rightState : FillState} (source : RandomSource.Related p pass lane slice index old s t)
+ (leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory)
+ (rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory)
+ (indices : (fillBlock p pass slice lane index leftState).indices =
+ (fillBlock p pass slice lane index rightState).indices) :
+ Related p pass lane slice index old leftState rightState s t := by
+ refine ⟨source, leftMatrix, rightMatrix, ?_⟩
+ intro mode
+ rw [Proof.Argon2.FillStep.indices p pass lane slice index leftState source.left.filling.bounds.active,
+ Proof.Argon2.FillStep.indices p pass lane slice index rightState source.right.filling.bounds.active] at indices
+ simp only [mode, Bool.false_eq_true, ite_false] at indices
+ exact (List.cons.inj indices).1
+
+theorem Related.reference_eq {p : Params} {pass lane slice index old : Nat} {s t : State}
+ {leftState rightState : FillState} (h : Related p pass lane slice index old leftState rightState s t) :
+ reference p pass lane slice index (Proof.Argon2.FillStep.random p pass lane slice index leftState.memory) =
+ reference p pass lane slice index (Proof.Argon2.FillStep.random p pass lane slice index rightState.memory) := by
+ cases mode : independent p pass slice
+ · exact h.references mode
+ · simp only [Proof.Argon2.FillStep.random, mode, ite_true]
+
+theorem source_public_rel (p : Params) (pass lane slice index old : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass lane slice index old leftState rightState)
+ Impl.Argon2.X86_64.RandomSource.code (FillKernel.Related p pass lane slice index) := by
+ intro s t ta tb a b hp ea eb
+ obtain ⟨traces, _⟩ := RandomSource.code_rel p pass lane slice index old _ _ _ _ _ _ hp.source ea eb
+ obtain ⟨_, a', runA, ha⟩ := RandomSource.code_ok s p pass lane slice index old hp.source.left leftState hp.leftMatrix
+ obtain ⟨_, b', runB, hb⟩ := RandomSource.code_ok t p pass lane slice index old hp.source.right rightState hp.rightMatrix
+ obtain ⟨_, sameA⟩ := Exec.det ea runA
+ obtain ⟨_, sameB⟩ := Exec.det eb runB
+ subst a'; subst b'
+ refine ⟨traces, ?_⟩
+ obtain ⟨_, readyA⟩ := ha.ready
+ obtain ⟨_, readyB⟩ := hb.ready
+ refine ⟨readyA.filling, readyB.filling, ?_, ?_, ?_, ?_, ?_⟩
+ · exact (ha.regs .rbp (by simp [calleeSaved])).trans
+ (hp.source.bases.trans (hb.regs .rbp (by simp [calleeSaved])).symm)
+ · exact (ha.regs .rsp (by simp [calleeSaved])).trans
+ (hp.source.stacks.trans (hb.regs .rsp (by simp [calleeSaved])).symm)
+ · exact (ha.frame_word hp.source.left 232 (by decide) (by decide)).trans
+ (hp.source.matrices.trans (hb.frame_word hp.source.right 232 (by decide) (by decide)).symm)
+ · exact (ha.frame_word hp.source.left 248 (by decide) (by decide)).trans
+ (hp.source.work.trans (hb.frame_word hp.source.right 248 (by decide) (by decide)).symm)
+ · rw [ha.random, hb.random]; exact hp.reference_eq
+
+theorem code_rel (p : Params) (pass lane slice index old : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass lane slice index old leftState rightState)
+ Impl.Argon2.X86_64.FillBlock.code (fun _ _ => True) :=
+ (source_public_rel p pass lane slice index old leftState rightState).seq
+ (FillKernel.code_rel p pass lane slice index)
+
+end VG.Proof.Argon2.X86_64.FillBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCounter.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCounter.lean
new file mode 100644
index 000000000..df6e8aac4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockCounter.lean
@@ -0,0 +1,26 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourceCounter
+
+/-! Compression preserves the public cache counter selected by the random source. -/
+
+namespace VG.Proof.Argon2.X86_64.FillBlock
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem counter_run {s t : State} {trace : List Leak} {p : Params} {pass lane slice index old : Nat}
+ (h : RandomSource.Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (run : Exec isa Impl.Argon2.X86_64.FillBlock.code s trace t) :
+ t.mem.readW (off (t.gpr .rbp) 8) 64 = RandomSource.counterValue p pass slice index old := by
+ cases run with
+ | seq sourceRun kernelRun =>
+ obtain ⟨_, a', runA, source⟩ := RandomSource.code_ok s p pass lane slice index old h state represented
+ obtain ⟨_, rfl⟩ := Exec.det sourceRun runA
+ obtain ⟨_, a', counterRun, counter⟩ := RandomSource.counter_ok s p pass lane slice index old h
+ obtain ⟨_, rfl⟩ := Exec.det sourceRun counterRun
+ obtain ⟨_, ready⟩ := source.ready
+ obtain ⟨_, t', runT, done⟩ := FillKernel.code_ok _ p pass lane slice index ready.filling
+ obtain ⟨_, rfl⟩ := Exec.det kernelRun runT
+ exact (done.frame_word ready.filling 8 (by decide) (by decide)).trans counter
+
+end VG.Proof.Argon2.X86_64.FillBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockFrame.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockFrame.lean
new file mode 100644
index 000000000..0243d7c91
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillBlockFrame.lean
@@ -0,0 +1,37 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourceState
+
+/-! Compose scratch writes with a matrix-cell write inside the derive allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.FillBlock
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def writes (s : State) (p : Params) : List Region :=
+ [⟨FillKernel.matrix s, p.blocks * 1024⟩, ⟨AddressCalls.work s, 8192⟩,
+ below (s.gpr .rsp) 8, ⟨off (s.gpr .rbp) 8, 16⟩]
+
+theorem source_frame {s t : State} {p : Params} {pass lane slice index : Nat} {state : FillState}
+ (done : RandomSource.Done s t p pass lane slice index state) : Frame (writes s p) s.mem t.mem := by
+ apply done.frame.sub
+ intro r hr
+ simp only [RandomSource.writes, AddressCache.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨⟨off (s.gpr .rbp) 8, 16⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+
+theorem kernel_frame {s t : State} {p : Params} {pass lane slice index : Nat}
+ (ready : FillKernel.Ready p pass lane slice index s) (done : FillKernel.Done s t p pass lane slice index) :
+ Frame (writes s p) s.mem t.mem := by
+ apply done.frame.sub
+ intro r hr
+ simp only [FillKernel.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact ⟨_, by simp [writes], FillKernel.cell_sub p _ ready.bounds.lanesPositive ready.bounds.laneBound
+ (Proof.Argon2.column_lt p ready.bounds.lanesPositive ready.bounds.sliceBound ready.bounds.indexBound)⟩
+ · exact ⟨⟨AddressCalls.work s, 8192⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], Offset.sub _ (d := 16) (n := 8) (e := 8) (k := 16)
+ (by decide) (by decide)⟩
+
+end VG.Proof.Argon2.X86_64.FillBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCacheInvariant.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCacheInvariant.lean
new file mode 100644
index 000000000..0876b3625
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCacheInvariant.lean
@@ -0,0 +1,57 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourcePrepare
+
+/-! The cell update does not disturb the cached independent-address block. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Ready.after_fill {p : Params} {pass lane slice index old : Nat} {s t : State}
+ (h : Ready p pass lane slice index old s) (done : FillKernel.Done s t p pass lane slice index) :
+ Ready p pass lane slice index old t := by
+ have bp := done.regs .rbp (by simp [calleeSaved])
+ have sp := done.regs .rsp (by simp [calleeSaved])
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word h.filling 232 (by decide) (by decide)
+ have work : AddressCalls.work t = AddressCalls.work s := done.frame_word h.filling 248 (by decide) (by decide)
+ refine ⟨done.retains h.filling, ?_, ?_⟩
+ · refine ⟨?_, ?_, ?_, ?_, h.cache.bound, ?_⟩
+ · constructor
+ · rw [done.rd, done.wr, bp]; exact h.cache.layout.frameRead
+ · rw [done.wr, work]; exact h.cache.layout.workWrite
+ · rw [bp, work]; exact h.cache.layout.frameWork
+ · rw [bp, sp]; exact h.cache.layout.frameStack
+ · rw [sp, work]; exact h.cache.layout.stackWork
+ · rw [done.rd, done.wr, bp]; exact h.cache.reads
+ · rw [done.wr, bp]; exact h.cache.write
+ · exact ⟨(done.frame_word h.filling 0 (by decide) (by decide)).trans h.cache.words.passWord,
+ (done.regs .rbx (by simp [calleeSaved])).trans h.cache.words.laneWord,
+ (done.regs .r14 (by simp [calleeSaved])).trans h.cache.words.sliceWord,
+ (done.frame_word h.filling 240 (by decide) (by decide)).trans h.cache.words.blocksWord,
+ (done.frame_word h.filling 72 (by decide) (by decide)).trans h.cache.words.passesWord,
+ (done.frame_word h.filling 112 (by decide) (by decide)).trans h.cache.words.variantWord,
+ (done.frame_word h.filling 8 (by decide) (by decide)).trans h.cache.words.counterWord⟩
+ · rcases h.cache.cached with zero | cached
+ · exact Or.inl zero
+ · apply Or.inr
+ rw [work]
+ have kept : blockAt t.mem (off (AddressCalls.work s) 6144) =
+ blockAt s.mem (off (AddressCalls.work s) 6144) := by
+ apply FillCompress.block_frame done.frame
+ intro r hr
+ simp only [FillKernel.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ have cacheSub : Region.Sub ⟨off (AddressCalls.work s) 6144, 1024⟩ ⟨AddressCalls.work s, 8192⟩ :=
+ Offset.sub_base _ (by decide)
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact (h.matrixWork.symm.sub_left cacheSub).sub_right
+ (FillKernel.cell_sub p _ h.filling.bounds.lanesPositive h.filling.bounds.laneBound
+ (Proof.Argon2.column_lt p h.filling.bounds.lanesPositive
+ h.filling.bounds.sliceBound h.filling.bounds.indexBound))
+ · exact Offset.disjoint_base (AddressCalls.work s) (d := 6144) (n := 1024) (k := 5120)
+ (by decide) (by decide)
+ · exact h.cache.layout.stackWork.symm.sub_left cacheSub
+ · exact (h.cache.layout.frameWork.symm.sub_left cacheSub).sub_right
+ (Offset.sub_base _ (by decide))
+ exact kept.trans cached
+ · rw [base, work]; exact h.matrixWork
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumn.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumn.lean
new file mode 100644
index 000000000..a146c8fae
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumn.lean
@@ -0,0 +1,164 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillColumn
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+import VerifiedGarbage.Proof.Argon2.Dimensions
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Current-column arithmetic and the cyclic predecessor, preserving the
+matrix, enclosing loop registers and MXCSR. -/
+
+namespace VG.Proof.Argon2.X86_64.FillColumn
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillColumn
+
+theorem current_ok (s : State) : WP isa (.block current) s fun t =>
+ t.gpr .rcx = s.gpr .r14 * s.gpr .r13 + s.gpr .r15 ∧
+ Divide.Keeps [.rax, .rdx, .rcx] s t := by
+ apply WP.of_runBlock
+ simp only [current, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ execMul, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_setFlags, RegUpd.gpr_arithFlags,
+ reduceCtorEq, ite_true, ite_false, Option.map_some, Option.bind_some,
+ Option.some.injEq, exists_eq_left', BitVec.ofNat_mul, BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_setFlags, RegUpd.gpr_arithFlags,
+ hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem compare_ok (s : State) : WP isa (.block [.alu .cmp .rcx (.imm 0)]) s
+ fun t => t.zf = decide (s.gpr .rcx = 0) ∧ Divide.Keeps [] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.zf_arithFlags, Option.bind_some, Option.some.injEq, exists_eq_left',
+ show BitVec.signExtend 64 (0 : BitVec 32) = (0 : Addr) from rfl]
+ refine ⟨?_, ?_⟩
+ · change (s.gpr .rcx - 0#64 == 0#64) = decide (s.gpr .rcx = 0#64)
+ rw [BitVec.sub_zero]
+ exact Bool.eq_iff_iff.mpr (by simp only [beq_iff_eq, decide_eq_true_eq])
+ constructor
+ · intro r _; exact congrFun (RegUpd.gpr_arithFlags _ _ _ _) r
+ all_goals rfl
+
+theorem move_ok (s : State) (r : Reg) : WP isa (.block [.mov .rdi (.reg r)]) s
+ fun t => t.gpr .rdi = s.gpr r ∧ Divide.Keeps [.rdi] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro q hq
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hq
+ exact ite_eq_right hq
+ all_goals rfl
+
+theorem decrement_ok (s : State) : WP isa (.block [.alu .sub .rdi (.imm 1)]) s
+ fun t => t.gpr .rdi = s.gpr .rdi - 1 ∧ Divide.Keeps [.rdi] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, Option.bind_some,
+ Option.some.injEq, exists_eq_left', ite_true,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ exact ite_eq_right hr
+ all_goals rfl
+
+theorem previous_ok (s : State) : WP isa previous s fun t =>
+ t.gpr .rdi = (if s.gpr .rcx = 0 then s.gpr .r12 else s.gpr .rcx) - 1 ∧
+ Divide.Keeps [.rdi] s t := by
+ unfold previous
+ refine WP.seq ((compare_ok s).mono ?_)
+ rintro a ⟨flag, ka⟩
+ have selected : WP isa select a fun b =>
+ b.gpr .rdi = (if s.gpr .rcx = 0 then s.gpr .r12 else s.gpr .rcx) ∧
+ Divide.Keeps [.rdi] s b := by
+ unfold select
+ refine WP.ite (decide (s.gpr .rcx = 0)) (by simp only [eval, flag]) ?_ ?_
+ · intro h
+ have zero := of_decide_eq_true h
+ refine (move_ok a .r12).mono ?_
+ rintro b ⟨value, kb⟩
+ exact ⟨by rw [value, ka.regs .r12 (by simp), ite_eq_left zero],
+ (ka.mono (by simp)).trans kb⟩
+ · intro h
+ have nonzero := of_decide_eq_false h
+ refine (move_ok a .rcx).mono ?_
+ rintro b ⟨value, kb⟩
+ exact ⟨by rw [value, ka.regs .rcx (by simp), ite_eq_right nonzero],
+ (ka.mono (by simp)).trans kb⟩
+ refine WP.seq (selected.mono ?_)
+ rintro b ⟨value, kb⟩
+ refine (decrement_ok b).mono ?_
+ rintro t ⟨result, kt⟩
+ exact ⟨by rw [result, value], kb.trans kt⟩
+
+theorem code_ok (s : State) : WP isa code s fun t =>
+ let column := s.gpr .r14 * s.gpr .r13 + s.gpr .r15
+ t.gpr .rcx = column ∧
+ t.gpr .rdi = (if column = 0 then s.gpr .r12 else column) - 1 ∧
+ Divide.Keeps [.rax, .rdx, .rcx, .rdi] s t := by
+ unfold code
+ refine WP.seq ((current_ok s).mono ?_)
+ rintro a ⟨column, ka⟩
+ refine (previous_ok a).mono ?_
+ rintro t ⟨previous, kt⟩
+ refine ⟨(kt.regs .rcx (by decide)).trans column, ?_,
+ (ka.mono (by simp)).trans (kt.mono (by simp))⟩
+ rw [previous, column, ka.regs .r12 (by decide)]
+
+theorem previous_nat (column q : Nat) (positive : 0 < q) (bound : column < q) :
+ (if column = 0 then q else column) - 1 = (column + q - 1) % q := by
+ by_cases zero : column = 0
+ · simp only [zero, ite_true, Nat.zero_add]
+ exact (Nat.mod_eq_of_lt (by omega : q - 1 < q)).symm
+ · simp only [zero, ite_false]
+ have sub : column + q - 1 - q = column - 1 := by omega
+ rw [Nat.mod_eq_sub_mod (by omega : q ≤ column + q - 1), sub,
+ Nat.mod_eq_of_lt (by omega : column - 1 < q)]
+
+theorem previous_word_nat (column q : Nat) (positive : 0 < q) (qBound : q < 2 ^ 64)
+ (bound : column < q) :
+ (if BitVec.ofNat 64 column = 0#64 then BitVec.ofNat 64 q else BitVec.ofNat 64 column) - 1 =
+ BitVec.ofNat 64 ((column + q - 1) % q) := by
+ have zero : BitVec.ofNat 64 column = 0#64 ↔
+ column = 0 := by
+ constructor
+ · intro h
+ have hn := congrArg BitVec.toNat h
+ rw [BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans bound qBound)] at hn
+ exact hn
+ · intro h; rw [h]
+ simp only [zero]
+ by_cases h : column = 0
+ · simp only [h, ite_true]
+ rw [show (1 : Addr) = BitVec.ofNat 64 1 from rfl,
+ Offset.ofNat_sub_ofNat positive, Nat.zero_add, Nat.mod_eq_of_lt (by omega : q - 1 < q)]
+ · simp only [h, ite_false]
+ rw [show (1 : Addr) = BitVec.ofNat 64 1 from rfl,
+ Offset.ofNat_sub_ofNat (by omega : 1 ≤ column),
+ ← previous_nat _ q positive bound, ite_eq_right h]
+
+theorem code_nat_ok (s : State) (slice segment index q : Nat)
+ (hs : s.gpr .r14 = BitVec.ofNat 64 slice)
+ (hg : s.gpr .r13 = BitVec.ofNat 64 segment)
+ (hi : s.gpr .r15 = BitVec.ofNat 64 index)
+ (hq : s.gpr .r12 = BitVec.ofNat 64 q)
+ (positive : 0 < q) (qBound : q < 2 ^ 64)
+ (bound : slice * segment + index < q) :
+ WP isa code s fun t =>
+ t.gpr .rcx = BitVec.ofNat 64 (slice * segment + index) ∧
+ t.gpr .rdi = BitVec.ofNat 64 ((slice * segment + index + q - 1) % q) ∧
+ Divide.Keeps [.rax, .rdx, .rcx, .rdi] s t := by
+ refine (code_ok s).mono ?_
+ rintro t ⟨column, previous, keeps⟩
+ have word : s.gpr .r14 * s.gpr .r13 + s.gpr .r15 =
+ BitVec.ofNat 64 (slice * segment + index) := by
+ rw [hs, hg, hi, ← BitVec.ofNat_mul, ← BitVec.ofNat_add]
+ refine ⟨column.trans word, ?_, keeps⟩
+ rw [previous, word, hq]
+ exact previous_word_nat _ q positive qBound bound
+
+end VG.Proof.Argon2.X86_64.FillColumn
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnCT.lean
new file mode 100644
index 000000000..b142465e6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnCT.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillColumnLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Public loop coordinates determine both columns and their branch trace. -/
+
+namespace VG.Proof.Argon2.X86_64.FillColumn
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillColumn
+
+theorem code_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.r14, .r13, .r15, .r12], s.gpr r = t.gpr r) code
+ (fun s t => ∀ r ∈ [Reg.rcx, .rdi], s.gpr r = t.gpr r) :=
+ RelCT.taintRegs (τ := Taint.ofRegs [.r14, .r13, .r15, .r12])
+ (fun _ _ h => Taint.agree_ofRegs h) [Reg.rcx, .rdi] (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.FillColumn
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnLit.lean
new file mode 100644
index 000000000..6d12ca925
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillColumnLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.FillColumn
+
+/-! A checked literal for current and cyclic predecessor column calculation. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.FillColumn.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompress.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompress.lean
new file mode 100644
index 000000000..726d9855c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompress.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressLit
+
+/-! The complete compression/update sequence from allocation and frame invariants. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillCompress
+
+def writes (s : State) : List Region :=
+ [⟨s.gpr .r10, 1024⟩, ⟨work s + 4096, 1024⟩, ⟨work s, 4096⟩,
+ below (s.gpr .rsp) 8, ⟨off (s.gpr .rbp) 16, 8⟩]
+
+structure Done (s t : State) : Prop where
+ block : blockAt t.mem (s.gpr .r10) =
+ let next := Spec.Argon2.compress (blockAt s.mem (s.gpr .rdi)) (blockAt s.mem (s.gpr .rsi))
+ if pass s = 0 then next else xorBlock next (blockAt s.mem (s.gpr .r10))
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s) s.mem t.mem
+
+theorem code_ok (s : State) (h : Ready s) : WP isa code s (Done s) := by
+ unfold code
+ refine WP.seq ((setup_ok s h).mono ?_)
+ intro a prepared
+ refine (operation_ok a prepared.ready).mono ?_
+ intro t done
+ refine ⟨?_, fun r hr => (done.regs r hr).trans (prepared.regs r hr),
+ done.rd.trans prepared.rd, done.wr.trans prepared.wr, ?_⟩
+ · have block := done.block
+ rw [prepared.oldBlock, prepared.dest, prepared.counter, prepared.leftBlock,
+ prepared.rightBlock] at block
+ exact block
+ · have frame : Frame (writes s) a.mem t.mem := by
+ have original := done.frame
+ rw [prepared.dest] at original
+ simp only [callWrites, prepared.output, prepared.scratch,
+ prepared.regs .rsp (by simp [calleeSaved])] at original
+ exact original.mono (by intro r hr; exact List.mem_append_left _ hr)
+ have savedFrame : Frame (writes s) s.mem a.mem := prepared.frame.mono (by
+ intro r hr
+ simp only [prefixWrites, List.mem_singleton] at hr
+ subst r
+ simp [writes])
+ exact savedFrame.trans frame
+
+theorem code_mx_ok (s : State) (h : Ready s) :
+ WP isa code s fun t => Done s t ∧ t.mxcsr = s.mxcsr :=
+ WP.mono_mx (by lit_decide) (code_ok s h) (fun _ done mx => ⟨done, mx⟩)
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressArgs.lean
new file mode 100644
index 000000000..09255a953
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressArgs.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillCompress
+import VerifiedGarbage.Proof.Argon2.X86_64.Memory
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! Save the current cell across G and reload the block-write arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillCompress
+
+theorem saveCurrent_ok (s : State)
+ (hw : InRegions s.wr (off (s.gpr .rbp) 16) 8) :
+ WP isa (.block saveCurrent) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rbp) 16) (s.gpr .r10) ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ apply WP.of_runBlock
+ simp only [saveCurrent, runBlock_cons, runStep_some, runBlock_nil, exec,
+ State.store64, ea_at, hw, ite_true, Option.some.injEq, exists_eq_left']
+ exact ⟨trivial, trivial, trivial, trivial, trivial⟩
+
+theorem compressArgs_ok (s : State)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8) :
+ WP isa (.block compressArgs) s fun t =>
+ t.gpr .rcx = s.mem.readW (off (s.gpr .rbp) 248) 64 ∧
+ t.gpr .rdx = s.mem.readW (off (s.gpr .rbp) 248) 64 + 4096 ∧
+ Divide.Keeps [.rcx, .rdx] s t := by
+ apply WP.of_runBlock
+ simp only [compressArgs, runBlock_cons, runStep_some, runBlock_nil, exec,
+ readSrc, State.load64, ea_at, hr, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ reduceCtorEq, ite_true, ite_false, Option.map_some, Option.bind_some,
+ Option.some.injEq, exists_eq_left',
+ show BitVec.signExtend 64 (4096 : BitVec 32) = (4096 : Addr) from rfl]
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem writeArgs_ok (s : State)
+ (destRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 16) 8)
+ (workRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 248) 8)
+ (passRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8) :
+ WP isa (.block writeArgs) s fun t =>
+ t.gpr .rdi = s.mem.readW (off (s.gpr .rbp) 16) 64 ∧
+ t.gpr .rsi = s.mem.readW (off (s.gpr .rbp) 248) 64 + 4096 ∧
+ t.gpr .r9 = s.mem.readW (off (s.gpr .rbp) 0) 64 ∧
+ Divide.Keeps [.rdi, .rsi, .r9] s t := by
+ apply WP.of_runBlock
+ simp only [writeArgs, runBlock_cons, runStep_some, runBlock_nil, exec,
+ readSrc, State.load64, ea_at, destRead, workRead, passRead, execAlu,
+ RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ RegUpd.gpr_arithFlags, RegUpd.mem_arithFlags, RegUpd.rd_arithFlags,
+ RegUpd.wr_arithFlags, reduceCtorEq, ite_true, ite_false,
+ Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left',
+ show BitVec.signExtend 64 (4096 : BitVec 32) = (4096 : Addr) from rfl]
+ refine ⟨trivial, trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCT.lean
new file mode 100644
index 000000000..dc9965321
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCT.lean
@@ -0,0 +1,54 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompress
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressOperationCT
+
+/-! Compose the setup trace with compression and the full block write. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillCompress
+
+structure CodeRelated (s t : State) : Prop where
+ left : Ready s
+ right : Ready t
+ args : ∀ r ∈ [Reg.rdi, .rsi, .r10, .rsp, .rbp], s.gpr r = t.gpr r
+ scratch : work s = work t
+ counter : pass s = pass t
+
+theorem setup_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ setup (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem prepared_public {s t a b : State} (h : CodeRelated s t)
+ (ha : Prepared s a) (hb : Prepared t b) : Related a b := by
+ refine ⟨ha.ready, hb.ready, ?_, ha.dest.trans ((h.args .r10 (by simp)).trans hb.dest.symm),
+ ha.counter.trans (h.counter.trans hb.counter.symm)⟩
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl | rfl
+ · exact ha.left.trans ((h.args .rdi (by simp)).trans hb.left.symm)
+ · exact ha.right.trans ((h.args .rsi (by simp)).trans hb.right.symm)
+ · exact ha.output.trans ((congrArg (· + (4096 : Addr)) h.scratch).trans hb.output.symm)
+ · exact ha.scratch.trans (h.scratch.trans hb.scratch.symm)
+ · exact (ha.regs .rsp (by simp [calleeSaved])).trans
+ ((h.args .rsp (by simp)).trans (hb.regs .rsp (by simp [calleeSaved])).symm)
+ · exact (ha.regs .rbp (by simp [calleeSaved])).trans
+ ((h.args .rbp (by simp)).trans (hb.regs .rbp (by simp [calleeSaved])).symm)
+
+theorem setup_public_rel : RelCT isa CodeRelated setup Related := by
+ have trace := setup_rel.mono (P' := CodeRelated)
+ (fun _ _ h => h.args .rbp (by simp)) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨setup_ok s h.left, setup_ok t h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact prepared_public hp ha hb
+
+theorem code_rel : RelCT isa CodeRelated code (fun _ _ => True) :=
+ setup_public_rel.seq operation_rel
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCall.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCall.lean
new file mode 100644
index 000000000..72355e261
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCall.lean
@@ -0,0 +1,101 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.Compress
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Invoke the verified compression primitive with narrowed permissions,
+retaining the surrounding matrix and derivation frame. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64
+
+structure CallReady (s : State) : Prop where
+ left : Covers [⟨s.gpr .rdi, 1024⟩] (s.rd ++ s.wr)
+ right : Covers [⟨s.gpr .rsi, 1024⟩] (s.rd ++ s.wr)
+ output : Covers [⟨s.gpr .rdx, 1024⟩] s.wr
+ scratch : Covers [⟨s.gpr .rcx, 4096⟩] s.wr
+ leftScratch : (⟨s.gpr .rdi, 1024⟩ : Region).Disjoint ⟨s.gpr .rcx, 4096⟩
+ rightScratch : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rcx, 4096⟩
+ outputScratch : (⟨s.gpr .rdx, 1024⟩ : Region).Disjoint ⟨s.gpr .rcx, 4096⟩
+ stackLeft : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rdi, 1024⟩
+ stackRight : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rsi, 1024⟩
+ stackOutput : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rdx, 1024⟩
+ stackScratch : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rcx, 4096⟩
+
+structure Called (s t : State) : Prop where
+ result : Spec.Argon2.blockAt t.mem (s.gpr .rdx) = Spec.Argon2.compress
+ (Spec.Argon2.blockAt s.mem (s.gpr .rdi)) (Spec.Argon2.blockAt s.mem (s.gpr .rsi))
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨s.gpr .rdx, 1024⟩, ⟨s.gpr .rcx, 4096⟩, below (s.gpr .rsp) 8] s.mem t.mem
+
+theorem noSp : NoSp Impl.Argon2.X86_64.compress := by
+ have h : Impl.Argon2.X86_64.compress.allInstrs (fun i => !Taint.clobbers i .rsp) = true :=
+ by lit_decide
+ rw [Code.allInstrs_eq, List.all_eq_true] at h
+ intro i hi
+ simpa only [Bool.not_eq_true'] using h i hi
+
+theorem depth : Impl.Argon2.X86_64.compress.depth = 0 := by lit_decide
+
+theorem call_hyps (s : State) (h : CallReady s) :
+ compressLocal.pre (s.callEntry.withRegions [⟨s.gpr .rdi, 1024⟩, ⟨s.gpr .rsi, 1024⟩]
+ [⟨s.gpr .rdx, 1024⟩, ⟨s.gpr .rcx, 4096⟩]) ∧
+ Covers [⟨s.gpr .rdi, 1024⟩, ⟨s.gpr .rsi, 1024⟩,
+ ⟨s.gpr .rdx, 1024⟩, ⟨s.gpr .rcx, 4096⟩] (s.rd ++ s.wr) ∧
+ Covers [⟨s.gpr .rdx, 1024⟩, ⟨s.gpr .rcx, 4096⟩] s.wr := by
+ have g : ∀ r, r ≠ .rsp → s.callEntry.gpr r = s.gpr r := fun _ hr => State.callEntry_gpr s hr
+ refine ⟨?_, ?_, ?_⟩
+ · simp only [compressLocal, State.withRegions_gpr, State.withRegions_rd,
+ State.withRegions_wr, g _ (by decide : Reg.rdi ≠ .rsp),
+ g _ (by decide : Reg.rsi ≠ .rsp), g _ (by decide : Reg.rdx ≠ .rsp),
+ g _ (by decide : Reg.rcx ≠ .rsp), State.callEntry_rsp]
+ exact ⟨trivial, trivial, h.outputScratch, h.leftScratch, h.rightScratch,
+ h.stackOutput, h.stackScratch⟩
+ · intro p n ⟨r, hr, hc⟩
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact h.left p n ⟨_, List.mem_singleton_self _, hc⟩
+ · exact h.right p n ⟨_, List.mem_singleton_self _, hc⟩
+ · obtain ⟨r, hr, hc⟩ := h.output p n ⟨_, List.mem_singleton_self _, hc⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ · obtain ⟨r, hr, hc⟩ := h.scratch p n ⟨_, List.mem_singleton_self _, hc⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ · intro p n ⟨r, hr, hc⟩
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.output p n ⟨_, List.mem_singleton_self _, hc⟩
+ · exact h.scratch p n ⟨_, List.mem_singleton_self _, hc⟩
+
+theorem callEntry_block (s : State) (p : Addr)
+ (h : (below (s.gpr .rsp) 8).Disjoint ⟨p, 1024⟩) :
+ Spec.Argon2.blockAt s.callEntry.mem p = Spec.Argon2.blockAt s.mem p := by
+ have frame : Frame [below (s.gpr .rsp) 8] s.mem s.callEntry.mem := by
+ rw [State.callEntry_mem]
+ exact (Frame.refl _ _).writeW (r := below (s.gpr .rsp) 8) (by simp) _
+ (below_call _ (by decide) (by decide))
+ apply Vector.ext
+ intro i hi
+ have read := frame.readW (r := ⟨p, 1024⟩) (a := off p (8 * i)) (w := 64)
+ (Offset.contains_base p (d := 8 * i) (n := 8) (k := 1024) (by omega) (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r; exact h.symm) (by decide)
+ rw [← blockAt_get s.callEntry.mem p ⟨i, hi⟩, ← blockAt_get s.mem p ⟨i, hi⟩] at read
+ exact read
+
+theorem call_ok (name : String) (s : State) (h : CallReady s) :
+ WP isa (.call name Impl.Argon2.X86_64.compress) s (Called s) := by
+ obtain ⟨pre, cover, writes⟩ := call_hyps s h
+ refine WP.call (k := compressLocal) compress_correct noSp (by rw [depth]; decide)
+ pre cover writes ?_
+ intro t rd wr regs frame _ ⟨u, memU, regsU, result⟩
+ change Spec.Argon2.blockAt u.mem (s.callEntry.gpr .rdx) = Spec.Argon2.compress
+ (Spec.Argon2.blockAt s.callEntry.mem (s.callEntry.gpr .rdi))
+ (Spec.Argon2.blockAt s.callEntry.mem (s.callEntry.gpr .rsi)) at result
+ rw [State.callEntry_gpr _ (by decide : Reg.rdx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rdi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rsi ≠ .rsp), memU,
+ callEntry_block s _ h.stackLeft, callEntry_block s _ h.stackRight] at result
+ rw [depth] at frame
+ exact ⟨result, regs, rd, wr, frame⟩
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCallCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCallCT.lean
new file mode 100644
index 000000000..62d0b93ec
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressCallCT.lean
@@ -0,0 +1,30 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCall
+
+/-! Compression calls reveal only their argument addresses and stack pointer. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64
+
+theorem call_rel (name : String) {P : State → State → Prop}
+ (pre : ∀ s t, P s t → CallReady s ∧ CallReady t ∧
+ s.gpr .rdi = t.gpr .rdi ∧ s.gpr .rsi = t.gpr .rsi ∧
+ s.gpr .rdx = t.gpr .rdx ∧ s.gpr .rcx = t.gpr .rcx ∧ s.gpr .rsp = t.gpr .rsp) :
+ RelCT isa P (.call name Impl.Argon2.X86_64.compress) (fun _ _ => True) := by
+ apply RelCT.callEx (k := compressLocal) compress_correct compress_ct
+ intro s t hp
+ obtain ⟨hs, ht, di, si, dx, cx, sp⟩ := pre s t hp
+ obtain ⟨ps, cs, ws⟩ := call_hyps s hs
+ obtain ⟨pt, ct, wt⟩ := call_hyps t ht
+ refine ⟨_, _, _, _, ps, pt, ?_, cs, ws, ct, wt, sp⟩
+ change s.callEntry.gpr .rdi = t.callEntry.gpr .rdi ∧
+ s.callEntry.gpr .rsi = t.callEntry.gpr .rsi ∧
+ s.callEntry.gpr .rdx = t.callEntry.gpr .rdx ∧
+ s.callEntry.gpr .rcx = t.callEntry.gpr .rcx
+ simp only [State.callEntry_gpr _ (by decide : Reg.rdi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rsi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rdx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rcx ≠ .rsp)]
+ exact ⟨di, si, dx, cx⟩
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressLit.lean
new file mode 100644
index 000000000..64e99d851
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressLit.lean
@@ -0,0 +1,11 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.FillCompress
+
+/-! Checked literals for compression and the enclosing argument setup. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.FillCompress.operation
+materialize_code Impl.Argon2.X86_64.FillCompress.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperation.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperation.lean
new file mode 100644
index 000000000..5bc25e566
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperation.lean
@@ -0,0 +1,101 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCall
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWriteCover
+
+/-! Compression followed by first/later-pass writing, preserving the frame
+slots and the old destination cell across the compression call. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillCompress
+
+def callWrites (s : State) : List Region :=
+ [⟨s.gpr .rdx, 1024⟩, ⟨s.gpr .rcx, 4096⟩, below (s.gpr .rsp) 8]
+
+def destination (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 16) 64
+
+def pass (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 0) 64
+
+structure OperationReady (s : State) : Prop where
+ call : CallReady s
+ frameRead : ∀ d ∈ [0, 16, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ workWord : s.mem.readW (off (s.gpr .rbp) 248) 64 = s.gpr .rcx
+ outputPointer : s.gpr .rcx + 4096 = s.gpr .rdx
+ destinationWrite : Covers [⟨destination s, 1024⟩] s.wr
+ frameSafe : ∀ r ∈ callWrites s, (⟨s.gpr .rbp, 272⟩ : Region).Disjoint r
+ destinationSafe : ∀ r ∈ callWrites s, (⟨destination s, 1024⟩ : Region).Disjoint r
+
+structure OperationDone (s t : State) : Prop where
+ block : blockAt t.mem (destination s) =
+ let next := Spec.Argon2.compress (blockAt s.mem (s.gpr .rdi)) (blockAt s.mem (s.gpr .rsi))
+ if pass s = 0 then next else xorBlock next (blockAt s.mem (destination s))
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (⟨destination s, 1024⟩ :: callWrites s) s.mem t.mem
+
+theorem frame_word {s t : State} (h : OperationReady s) (called : Called s t)
+ (d : Nat) (hd : d + 8 ≤ 272) :
+ t.mem.readW (off (s.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 :=
+ called.frame.readW (r := ⟨s.gpr .rbp, 272⟩)
+ (Offset.contains_base _ hd (by omega)) h.frameSafe (by decide)
+
+theorem destination_unchanged {s t : State} (h : OperationReady s) (called : Called s t) :
+ blockAt t.mem (destination s) = blockAt s.mem (destination s) := by
+ apply Vector.ext
+ intro i hi
+ have read : t.mem.readW (off (destination s) (8 * i)) 64 =
+ s.mem.readW (off (destination s) (8 * i)) 64 :=
+ called.frame.readW (r := ⟨destination s, 1024⟩)
+ (Offset.contains_base _ (by omega) (by omega)) h.destinationSafe (by decide)
+ rw [← blockAt_get t.mem (destination s) ⟨i, hi⟩,
+ ← blockAt_get s.mem (destination s) ⟨i, hi⟩] at read
+ exact read
+
+theorem operation_ok (s : State) (h : OperationReady s) :
+ WP isa operation s (OperationDone s) := by
+ unfold operation
+ refine WP.seq ((call_ok _ s h.call).mono ?_)
+ intro a called
+ have bp : a.gpr .rbp = s.gpr .rbp := called.regs .rbp (by simp [calleeSaved])
+ have reads (d : Nat) (hd : d ∈ [0, 16, 248]) :
+ InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) d) 8 := by
+ rw [called.rd, called.wr, bp]; exact h.frameRead d hd
+ refine WP.seq ((writeArgs_ok a (reads 16 (by simp)) (reads 248 (by simp))
+ (reads 0 (by simp))).mono ?_)
+ rintro b ⟨dest, src, counter, keeps⟩
+ have dest' : b.gpr .rdi = destination s := by
+ rw [dest, bp, frame_word h called 16 (by decide), destination]
+ have src' : b.gpr .rsi = s.gpr .rdx := by
+ rw [src, bp, frame_word h called 248 (by decide), h.workWord, h.outputPointer]
+ have counter' : b.gpr .r9 = pass s := by
+ rw [counter, bp, frame_word h called 0 (by decide), pass]
+ have readable : Covers [⟨b.gpr .rsi, 1024⟩] (b.rd ++ b.wr) := by
+ rw [src', keeps.rd, keeps.wr, called.rd, called.wr]
+ intro p n hp
+ obtain ⟨r, hr, hc⟩ := h.call.output p n hp
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ have writable : Covers [⟨b.gpr .rdi, 1024⟩] b.wr := by
+ rw [dest', keeps.wr, called.wr]; exact h.destinationWrite
+ have sep : (⟨b.gpr .rsi, 1024⟩ : Region).Disjoint ⟨b.gpr .rdi, 1024⟩ := by
+ rw [src', dest']; exact (h.destinationSafe _ (by simp [callWrites])).symm
+ refine (FillWrite.code_cover_ok b readable writable sep).mono ?_
+ rintro t ⟨value, frame, tk, _⟩
+ refine ⟨?_, ?_, tk.2.1.trans (keeps.rd.trans called.rd),
+ tk.2.2.trans (keeps.wr.trans called.wr), ?_⟩
+ · rw [dest', src', counter', keeps.mem, called.result, destination_unchanged h called] at value
+ exact value
+ · intro r hr
+ have ne : r ≠ .rax := by
+ 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
+ have nk : r ∉ [Reg.rdi, .rsi, .r9] := by
+ 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 (tk.1 r ne).trans ((keeps.regs r nk).trans (called.regs r hr))
+ · rw [dest'] at frame
+ rw [keeps.mem] at frame
+ exact (called.frame.mono (by intro r hr; exact List.mem_cons_of_mem _ hr)).trans
+ (frame.mono (by simp))
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationCT.lean
new file mode 100644
index 000000000..28cbeeb1d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationCT.lean
@@ -0,0 +1,87 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressOperation
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCallCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWriteCT
+
+/-! Only the compression argument addresses, public frame words and stack
+pointer determine the compression-and-write trace. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillCompress
+
+structure Related (s t : State) : Prop where
+ left : OperationReady s
+ right : OperationReady t
+ args : ∀ r ∈ [Reg.rdi, .rsi, .rdx, .rcx, .rsp, .rbp], s.gpr r = t.gpr r
+ dest : destination s = destination t
+ counter : pass s = pass t
+
+structure BeforeWrite (s t : State) : Prop where
+ leftRead : ∀ d ∈ [0, 16, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ rightRead : ∀ d ∈ [0, 16, 248], InRegions (t.rd ++ t.wr) (off (t.gpr .rbp) d) 8
+ bases : s.gpr .rbp = t.gpr .rbp
+ words : ∀ d ∈ [0, 16, 248],
+ s.mem.readW (off (s.gpr .rbp) d) 64 = t.mem.readW (off (t.gpr .rbp) d) 64
+
+theorem called_public {s t a b : State} (hp : Related s t)
+ (ha : Called s a) (hb : Called t b) : BeforeWrite a b := by
+ have abp : a.gpr .rbp = s.gpr .rbp := ha.regs .rbp (by simp [calleeSaved])
+ have bbp : b.gpr .rbp = t.gpr .rbp := hb.regs .rbp (by simp [calleeSaved])
+ refine ⟨?_, ?_, abp.trans ((hp.args .rbp (by simp)).trans bbp.symm), ?_⟩
+ · intro d hd
+ rw [ha.rd, ha.wr, abp]; exact hp.left.frameRead d hd
+ · intro d hd
+ rw [hb.rd, hb.wr, bbp]; exact hp.right.frameRead d hd
+ · intro d hd
+ rw [abp, bbp]
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hd
+ rcases hd with rfl | rfl | rfl
+ · rw [frame_word hp.left ha 0 (by decide), frame_word hp.right hb 0 (by decide)]
+ exact hp.counter
+ · rw [frame_word hp.left ha 16 (by decide), frame_word hp.right hb 16 (by decide)]
+ exact hp.dest
+ · rw [frame_word hp.left ha 248 (by decide), frame_word hp.right hb 248 (by decide),
+ hp.left.workWord, hp.right.workWord]
+ exact hp.args .rcx (by simp)
+
+theorem call_public_rel : RelCT isa Related
+ (.call Spec.Argon2.compressApi.name VG.Impl.Argon2.X86_64.compress) BeforeWrite := by
+ have trace := call_rel Spec.Argon2.compressApi.name (P := Related) (fun _ _ hp =>
+ ⟨hp.left.call, hp.right.call, hp.args .rdi (by simp), hp.args .rsi (by simp),
+ hp.args .rdx (by simp), hp.args .rcx (by simp), hp.args .rsp (by simp)⟩)
+ have full := trace.wpDep (fun s t hp =>
+ ⟨call_ok _ s hp.left.call, call_ok _ t hp.right.call⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact called_public hp ha hb
+
+theorem writeArgs_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block writeArgs) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem writeArgs_public_rel : RelCT isa BeforeWrite (.block writeArgs)
+ (fun s t => ∀ r ∈ [Reg.r9, .rdi, .rsi], s.gpr r = t.gpr r) := by
+ have trace := writeArgs_rel.mono (P' := BeforeWrite) (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t hp =>
+ ⟨writeArgs_ok s (hp.leftRead 16 (by simp)) (hp.leftRead 248 (by simp)) (hp.leftRead 0 (by simp)),
+ writeArgs_ok t (hp.rightRead 16 (by simp)) (hp.rightRead 248 (by simp)) (hp.rightRead 0 (by simp))⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact ha.2.2.1.trans ((hp.words 0 (by simp)).trans hb.2.2.1.symm)
+ · exact ha.1.trans ((hp.words 16 (by simp)).trans hb.1.symm)
+ · exact ha.2.1.trans ((congrArg (· + (4096 : Addr)) (hp.words 248 (by simp))).trans hb.2.1.symm)
+
+theorem operation_rel : RelCT isa Related operation (fun _ _ => True) :=
+ call_public_rel.seq (writeArgs_public_rel.seq FillWrite.code_rel)
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationMx.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationMx.lean
new file mode 100644
index 000000000..cc6069fef
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressOperationMx.lean
@@ -0,0 +1,14 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressOperation
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressLit
+
+/-! Baseline compression and the block write preserve all of MXCSR. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillCompress
+
+theorem operation_mx_ok (s : State) (h : OperationReady s) :
+ WP isa operation s fun t => OperationDone s t ∧ t.mxcsr = s.mxcsr :=
+ WP.mono_mx (by lit_decide) (operation_ok s h) (fun _ done mx => ⟨done, mx⟩)
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressSetup.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressSetup.lean
new file mode 100644
index 000000000..6e3dce766
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillCompressSetup.lean
@@ -0,0 +1,162 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressOperation
+
+/-! Establish compression-and-write invariants from the frame and allocations. -/
+
+namespace VG.Proof.Argon2.X86_64.FillCompress
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillCompress
+
+def work (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 248) 64
+
+def prefixWrites (s : State) : List Region := [⟨off (s.gpr .rbp) 16, 8⟩]
+
+structure Ready (s : State) : Prop where
+ frameRead : ∀ d ∈ [0, 16, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ frameWrite : InRegions s.wr (off (s.gpr .rbp) 16) 8
+ leftRead : Covers [⟨s.gpr .rdi, 1024⟩] (s.rd ++ s.wr)
+ rightRead : Covers [⟨s.gpr .rsi, 1024⟩] (s.rd ++ s.wr)
+ destinationWrite : Covers [⟨s.gpr .r10, 1024⟩] s.wr
+ workWrite : Covers [⟨work s, 5120⟩] s.wr
+ leftWork : (⟨s.gpr .rdi, 1024⟩ : Region).Disjoint ⟨work s, 5120⟩
+ rightWork : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨work s, 5120⟩
+ destinationWork : (⟨s.gpr .r10, 1024⟩ : Region).Disjoint ⟨work s, 5120⟩
+ frameWork : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨work s, 5120⟩
+ leftFrame : (⟨s.gpr .rdi, 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩
+ rightFrame : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩
+ destinationFrame : (⟨s.gpr .r10, 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩
+ stackLeft : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rdi, 1024⟩
+ stackRight : (below (s.gpr .rsp) 8).Disjoint ⟨s.gpr .rsi, 1024⟩
+ stackWork : (below (s.gpr .rsp) 8).Disjoint ⟨work s, 5120⟩
+ destinationStack : (⟨s.gpr .r10, 1024⟩ : Region).Disjoint (below (s.gpr .rsp) 8)
+ frameStack : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint (below (s.gpr .rsp) 8)
+
+structure Prepared (s t : State) : Prop where
+ ready : OperationReady t
+ dest : destination t = s.gpr .r10
+ counter : pass t = pass s
+ scratch : t.gpr .rcx = work s
+ output : t.gpr .rdx = work s + 4096
+ left : t.gpr .rdi = s.gpr .rdi
+ right : t.gpr .rsi = s.gpr .rsi
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (prefixWrites s) s.mem t.mem
+ leftBlock : blockAt t.mem (t.gpr .rdi) = blockAt s.mem (s.gpr .rdi)
+ rightBlock : blockAt t.mem (t.gpr .rsi) = blockAt s.mem (s.gpr .rsi)
+ oldBlock : blockAt t.mem (destination t) = blockAt s.mem (s.gpr .r10)
+
+theorem block_frame {m m' : Mem} {rs : List Region} (hf : Frame rs m m')
+ (p : Addr) (sep : ∀ r ∈ rs, (⟨p, 1024⟩ : Region).Disjoint r) :
+ blockAt m' p = blockAt m p := by
+ apply Vector.ext
+ intro i hi
+ have read : m'.readW (off p (8 * i)) 64 = m.readW (off p (8 * i)) 64 :=
+ hf.readW (r := ⟨p, 1024⟩) (Offset.contains_base p (by omega) (by omega)) sep (by decide)
+ rw [← blockAt_get m' p ⟨i, hi⟩, ← blockAt_get m p ⟨i, hi⟩] at read
+ exact read
+
+theorem work_cover (s : State) (h : Ready s) (d n : Nat) (hd : d + n ≤ 5120) :
+ Covers [⟨off (work s) d, n⟩] s.wr := by
+ have sub : Covers [⟨off (work s) d, n⟩] [⟨work s, 5120⟩] := by
+ apply Covers.of_sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨work s, 5120⟩, by simp, d, rfl, hd⟩
+ exact fun p n hp => h.workWrite p n (sub p n hp)
+
+theorem prepared_of_setup (s a b : State) (h : Ready s)
+ (mem : a.mem = s.mem.writeW (off (s.gpr .rbp) 16) (s.gpr .r10))
+ (regs : a.gpr = s.gpr) (rd : a.rd = s.rd) (wr : a.wr = s.wr)
+ (scratch : b.gpr .rcx = a.mem.readW (off (a.gpr .rbp) 248) 64)
+ (output : b.gpr .rdx = a.mem.readW (off (a.gpr .rbp) 248) 64 + 4096)
+ (keeps : Divide.Keeps [.rcx, .rdx] a b) : Prepared s b := by
+ have g (r : Reg) (hr : r ∉ [Reg.rcx, .rdx]) : b.gpr r = s.gpr r :=
+ (keeps.regs r hr).trans (congrFun regs r)
+ have brd : b.rd = s.rd := keeps.rd.trans rd
+ have bwr : b.wr = s.wr := keeps.wr.trans wr
+ have bm : b.mem = s.mem.writeW (off (s.gpr .rbp) 16) (s.gpr .r10) := keeps.mem.trans mem
+ have unchanged (d : Nat) (sep : d + 8 ≤ 16 ∨ 24 ≤ d) (bound : d + 8 ≤ 272) :
+ b.mem.readW (off (b.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [bm, g .rbp (by decide)]
+ exact Mem.readW_writeW_sep (Offset.sep _ sep (by omega) (by decide)) (by decide)
+ have work' : b.gpr .rcx = work s := by
+ rw [scratch, regs, mem]
+ exact Mem.readW_writeW_sep (Offset.sep _ (by decide) (by decide) (by decide)) (by decide)
+ have out' : b.gpr .rdx = work s + 4096 := by
+ rw [output, regs, mem,
+ Mem.readW_writeW_sep (Offset.sep _ (by decide) (by decide) (by decide)) (by decide), work]
+ have dest : destination b = s.gpr .r10 := by
+ unfold destination
+ rw [bm, g .rbp (by decide), Mem.readW_writeW_self64]
+ have counter : pass b = pass s := unchanged 0 (by decide) (by decide)
+ have frame : Frame (prefixWrites s) s.mem b.mem := by
+ rw [bm]
+ exact (Frame.refl _ _).writeW (r := ⟨off (s.gpr .rbp) 16, 8⟩) (by simp [prefixWrites]) _
+ (Region.contains_self _ _)
+ have cellFrame (p : Addr) (sep : (⟨p, 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩) :
+ blockAt b.mem p = blockAt s.mem p :=
+ block_frame frame p (by
+ intro r hr
+ simp only [prefixWrites, List.mem_singleton] at hr
+ subst r
+ exact sep.sub_right (Offset.sub_base _ (by decide)))
+ have tempSub : Region.Sub ⟨work s + 4096, 1024⟩ ⟨work s, 5120⟩ :=
+ Offset.sub_base _ (by decide)
+ have scratchSub : Region.Sub ⟨work s, 4096⟩ ⟨work s, 5120⟩ := Region.sub_prefix (by decide)
+ refine ⟨?_, dest, counter, work', out', g .rdi (by decide), g .rsi (by decide), ?_, brd, bwr,
+ frame, ?_, ?_, ?_⟩
+ · refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [g .rdi (by decide), brd, bwr]; exact h.leftRead
+ · rw [g .rsi (by decide), brd, bwr]; exact h.rightRead
+ · rw [out', bwr]; exact work_cover s h 4096 1024 (by decide)
+ · rw [work', bwr]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero]
+ using work_cover s h 0 4096 (by decide)
+ · rw [g .rdi (by decide), work']; exact h.leftWork.sub_right scratchSub
+ · rw [g .rsi (by decide), work']; exact h.rightWork.sub_right scratchSub
+ · rw [out', work']; exact Offset.disjoint_base _ (by decide) (by decide)
+ · rw [g .rsp (by decide), g .rdi (by decide)]; exact h.stackLeft
+ · rw [g .rsp (by decide), g .rsi (by decide)]; exact h.stackRight
+ · rw [g .rsp (by decide), out']; exact h.stackWork.sub_right tempSub
+ · rw [g .rsp (by decide), work']; exact h.stackWork.sub_right scratchSub
+ · intro d hd
+ rw [brd, bwr, g .rbp (by decide)]; exact h.frameRead d hd
+ · rw [unchanged 248 (by decide) (by decide), work']; rfl
+ · rw [work', out']
+ · rw [dest, bwr]; exact h.destinationWrite
+ · intro r hr
+ simp only [callWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · rw [g .rbp (by decide), out']; exact h.frameWork.sub_right tempSub
+ · rw [g .rbp (by decide), work']; exact h.frameWork.sub_right scratchSub
+ · rw [g .rbp (by decide), g .rsp (by decide)]; exact h.frameStack
+ · intro r hr
+ rw [dest]
+ simp only [callWrites, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · rw [out']; exact h.destinationWork.sub_right tempSub
+ · rw [work']; exact h.destinationWork.sub_right scratchSub
+ · rw [g .rsp (by decide)]; exact h.destinationStack
+ · intro r hr
+ apply g r
+ 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
+ · rw [g .rdi (by decide)]; exact cellFrame _ h.leftFrame
+ · rw [g .rsi (by decide)]; exact cellFrame _ h.rightFrame
+ · rw [dest]; exact cellFrame _ h.destinationFrame
+
+theorem setup_ok (s : State) (h : Ready s) :
+ WP isa setup s (Prepared s) := by
+ unfold setup
+ refine WP.seq ((saveCurrent_ok s h.frameWrite).mono ?_)
+ rintro a ⟨mem, regs, rd, wr, _⟩
+ have read : InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) 248) 8 := by
+ rw [rd, wr, regs]; exact h.frameRead 248 (by simp)
+ refine (compressArgs_ok a read).mono ?_
+ rintro b ⟨scratch, output, keeps⟩
+ exact prepared_of_setup s a b h mem regs rd wr scratch output keeps
+
+end VG.Proof.Argon2.X86_64.FillCompress
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillContext.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillContext.lean
new file mode 100644
index 000000000..2bbb6335d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillContext.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegment
+
+/-! A segment context allows its starting and final indices, including an empty suffix. -/
+
+namespace VG.Proof.Argon2.X86_64.FillContext
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Parameters (p : Params) (pass lane slice : Nat) : Prop where
+ lanesPositive : 0 < p.lanes
+ lanesBound : p.lanes < 2 ^ 32
+ memoryMinimum : 8 * p.lanes ≤ p.memory
+ memoryBound : p.memory < 2 ^ 32
+ passBound : pass < 2 ^ 32
+ laneBound : lane < p.lanes
+ sliceBound : slice < 4
+
+structure Ready (p : Params) (pass lane slice index old : Nat) (s : State) : Prop where
+ parameters : Parameters p pass lane slice
+ layout : FillKernel.Layout p s
+ cache : AddressCache.Invariant p pass lane slice old s
+ matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩
+ position : ReferenceMap.Position p lane slice index s
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+theorem Ready.activate {p : Params} {pass lane slice index old : Nat} {s : State}
+ (h : Ready p pass lane slice index old s) (bound : index < p.segmentLen)
+ (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index) : RandomSource.Ready p pass lane slice index old s :=
+ ⟨⟨h.layout, ⟨h.parameters.lanesPositive, h.parameters.lanesBound, h.parameters.memoryMinimum,
+ h.parameters.memoryBound, h.parameters.passBound, h.parameters.laneBound, h.parameters.sliceBound,
+ bound, active⟩, h.position, h.cache.words.passWord, h.lanesWord⟩, h.cache, h.matrixWork⟩
+
+theorem Parameters.segment_bound {p : Params} {pass lane slice : Nat} (h : Parameters p pass lane slice) :
+ 2 ≤ p.segmentLen ∧ p.segmentLen < 2 ^ 64 := by
+ have minimum := Proof.Argon2.segmentLen_ge_two p h.lanesPositive h.memoryMinimum
+ have le : p.segmentLen ≤ p.blocks := by
+ have blocks := Proof.Argon2.blocks_lanes p h.lanesPositive
+ have segments := Proof.Argon2.laneLen_segments p h.lanesPositive
+ have laneLe : p.laneLen ≤ p.blocks := by rw [blocks]; exact Nat.le_mul_of_pos_left _ h.lanesPositive
+ omega
+ exact ⟨minimum, Nat.lt_of_le_of_lt le
+ (Nat.lt_trans (Nat.lt_of_le_of_lt (Proof.Argon2.blocks_le_memory p) h.memoryBound) (by decide))⟩
+
+theorem Ready.of_keeps {p : Params} {pass lane slice index old : Nat} {s t : State}
+ (h : Ready p pass lane slice index old s) (k : Divide.Keeps ReferenceMap.changed s t) :
+ Ready p pass lane slice index old t := by
+ have bp := k.regs .rbp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [k.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [k.mem, bp]
+ refine ⟨h.parameters, h.layout.of_preserved bp (k.regs .rsp (by decide)) base work k.rd k.wr,
+ h.cache.of_keeps k, ?_, h.position.of_keeps k, ?_⟩
+ · rw [base, work]; exact h.matrixWork
+ · rw [k.mem, bp]; exact h.lanesWord
+
+theorem finished_context {p : Params} {pass lane slice : Nat} {s t : State} {state : FillState}
+ (parameters : Parameters p pass lane slice) (h : FillSegment.Finished s t p pass lane slice state) :
+ ∃ old, Ready p pass lane slice p.segmentLen old t := by
+ obtain ⟨old, cache⟩ := h.cache
+ exact ⟨old, parameters, h.layout, cache, h.matrixWork, h.position, h.lanesWord⟩
+
+end VG.Proof.Argon2.X86_64.FillContext
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinish.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinish.lean
new file mode 100644
index 000000000..02b4dcc16
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinish.lean
@@ -0,0 +1,54 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillFinish
+import VerifiedGarbage.Proof.Argon2.X86_64.FillFinishReady
+import VerifiedGarbage.Proof.Argon2.X86_64.FinishStage
+
+/-! The complete filling and finalization stages produce the reviewed final tag. -/
+
+namespace VG.Proof.Argon2.X86_64.FillFinish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+open VG.Spec.Blake2 (bytesAt)
+
+structure Ready (p : Params) (s : State) : Prop where
+ filling : FillIterations.Ready p 0 s
+ finish : Finish.Ready p s
+ positive : 0 < p.passes
+
+def writes (s : State) (p : Params) : List Region := FillIterations.writes s p ++ Finish.writes s p
+
+structure Done (s t : State) (p : Params) (state : FillState) : Prop where
+ digest : bytesAt t.mem (FinalOutput.output s) p.tagLen =
+ Spec.Argon2.finish p (Proof.Argon2.iterations p 0 p.passes state).memory
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r14 → r ≠ .r15 → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+
+theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (p : Params)
+ (h : Ready p s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks state.memory) :
+ WP isa (Impl.Argon2.X86_64.FillFinish.code name (HPrime.hash v)) s (Done s · p state) := by
+ unfold Impl.Argon2.X86_64.FillFinish.code
+ refine WP.seq ((FillIterations.loop_ok p.passes s p 0 h.filling state represented h.positive (Nat.zero_add _)).mono ?_)
+ intro a filled
+ refine (Finish.code_ok v name a p (finish_ready h.filling h.finish filled) _ filled.represented).mono ?_
+ intro t finished
+ have output : FinalOutput.output a = FinalOutput.output s := filled.frame_word h.filling 256 (by decide) (by decide)
+ have work : FinalOutput.work a = FinalOutput.work s := filled.frame_word h.filling 248 (by decide) (by decide)
+ have base : matrix a = matrix s := filled.matrix
+ refine ⟨?_, fun r hr bx sl ix => (finished.regs r hr bx).trans (filled.regs r hr bx sl ix),
+ finished.rd.trans filled.rd, finished.wr.trans filled.wr, ?_⟩
+ · have digest := finished.digest
+ rw [output] at digest; exact digest
+ · have firstFrame : Frame (writes s p) s.mem a.mem := filled.frame.sub (by
+ intro r hr
+ exact ⟨r, List.mem_append_left _ hr, fun _ h => h⟩)
+ have lastFrame := finished.frame
+ rw [Finish.writes, base, output, work,
+ filled.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)] at lastFrame
+ apply firstFrame.trans
+ apply lastFrame.sub
+ intro r hr
+ exact ⟨r, List.mem_append_right _ hr, fun _ h => h⟩
+
+end VG.Proof.Argon2.X86_64.FillFinish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishCT.lean
new file mode 100644
index 000000000..63afa599a
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishCT.lean
@@ -0,0 +1,60 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillFinish
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FinishStageCT
+
+/-! Filling and finalization expose only the reviewed filling reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillFinish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Related (p : Params) (leftState rightState : FillState) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : matrix s = matrix t
+ outputs : FinalOutput.output s = FinalOutput.output t
+ works : FinalOutput.work s = FinalOutput.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.iterations p 0 p.passes leftState).indices =
+ (Proof.Argon2.iterations p 0 p.passes rightState).indices
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params)
+ (leftState rightState : FillState) :
+ RelCT isa (Related p leftState rightState) (Impl.Argon2.X86_64.FillFinish.code name (HPrime.hash v))
+ (fun _ _ => True) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq fillA finishA =>
+ cases eb with
+ | seq fillB finishB =>
+ have related : FillIterations.Related p 0 p.passes leftState rightState s t :=
+ ⟨⟨hp.left.filling, hp.right.filling, hp.bases, hp.stacks, hp.matrices, hp.works, hp.leftMatrix, hp.rightMatrix⟩,
+ hp.leftMatrix, hp.rightMatrix, hp.indices⟩
+ obtain ⟨fillTrace, _⟩ := FillIterations.loop_rel p 0 p.passes leftState rightState hp.left.positive
+ (Nat.zero_add _) _ _ _ _ _ _ related fillA fillB
+ obtain ⟨_, sa, runA, doneA⟩ := FillIterations.loop_ok p.passes s p 0 hp.left.filling leftState
+ hp.leftMatrix hp.left.positive (Nat.zero_add _)
+ obtain ⟨_, sb, runB, doneB⟩ := FillIterations.loop_ok p.passes t p 0 hp.right.filling rightState
+ hp.rightMatrix hp.right.positive (Nat.zero_add _)
+ obtain ⟨_, rfl⟩ := Exec.det fillA runA
+ obtain ⟨_, rfl⟩ := Exec.det fillB runB
+ have finalRelated : Finish.Related p (Proof.Argon2.iterations p 0 p.passes leftState).memory
+ (Proof.Argon2.iterations p 0 p.passes rightState).memory _ _ :=
+ ⟨finish_ready hp.left.filling hp.left.finish doneA, finish_ready hp.right.filling hp.right.finish doneB,
+ (doneA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm),
+ (doneA.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.stacks.trans (doneB.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm),
+ doneA.matrix.trans (hp.matrices.trans doneB.matrix.symm),
+ (doneA.frame_word hp.left.filling 256 (by decide) (by decide)).trans
+ (hp.outputs.trans (doneB.frame_word hp.right.filling 256 (by decide) (by decide)).symm),
+ (doneA.frame_word hp.left.filling 248 (by decide) (by decide)).trans
+ (hp.works.trans (doneB.frame_word hp.right.filling 248 (by decide) (by decide)).symm),
+ doneA.represented, doneB.represented⟩
+ obtain ⟨finishTrace, _⟩ := Finish.code_rel v name p _ _ _ _ _ _ _ _ finalRelated finishA finishB
+ exact ⟨by rw [fillTrace, finishTrace], trivial⟩
+
+end VG.Proof.Argon2.X86_64.FillFinish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishReady.lean
new file mode 100644
index 000000000..b6dc2b481
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillFinishReady.lean
@@ -0,0 +1,39 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterations
+import VerifiedGarbage.Proof.Argon2.X86_64.FinishReady
+
+/-! The complete filling loop retains the original final-call allocations and public metadata. -/
+
+namespace VG.Proof.Argon2.X86_64.FillFinish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+theorem finish_ready {s t : State} {p : Params} {state : FillState}
+ (filling : FillIterations.Ready p 0 s) (ready : Finish.Ready p s)
+ (done : FillIterations.Finished s t p state) : Finish.Ready p t := by
+ have bp := done.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)
+ have sp := done.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)
+ have base : matrix t = matrix s := done.matrix
+ have output : FinalOutput.output t = FinalOutput.output s := done.frame_word filling 256 (by decide) (by decide)
+ have work : FinalOutput.work t = FinalOutput.work s := done.frame_word filling 248 (by decide) (by decide)
+ constructor
+ · have a := ready.reduction.allocation
+ refine ⟨⟨a.positive, a.minimum, a.bound, ?_, ?_, ?_, ?_⟩, ready.reduction.lanesBound, ?_, ?_⟩
+ · rw [done.rd, done.wr, bp]; exact a.read
+ · rw [base, done.wr]; exact a.write
+ · rw [base, bp]; exact a.frame
+ · exact (done.regs .r12 (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans a.length
+ · rw [done.rd, done.wr, bp]; exact ready.reduction.lanesRead
+ · exact (done.frame_word filling 184 (by decide) (by decide)).trans ready.reduction.lanesWord
+ · refine ⟨ready.output.positive, ready.output.bound, ?_,
+ (done.frame_word filling 264 (by decide) (by decide)).trans ready.output.tagWord, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [done.rd, done.wr, bp]; exact ready.output.reads
+ · rw [base, done.rd, done.wr]; exact ready.output.input
+ · rw [output, done.wr]; exact ready.output.outputWrite
+ · rw [work, done.wr]; exact ready.output.workWrite
+ · rw [base, work]; exact ready.output.inputWork
+ · rw [output, work]; exact ready.output.outputWork
+ · rw [sp, base]; exact ready.output.stackInput
+ · rw [sp, output]; exact ready.output.stackOutput
+ · rw [sp, work]; exact ready.output.stackWork
+
+end VG.Proof.Argon2.X86_64.FillFinish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillHeader.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillHeader.lean
new file mode 100644
index 000000000..531a38abe
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillHeader.lean
@@ -0,0 +1,71 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanes
+
+/-! Allocation and normalized header across lane, slice and pass boundaries. -/
+
+namespace VG.Proof.Argon2.X86_64.FillHeader
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (pass lane slice : Nat) (s : State) : Prop where
+ layout : FillKernel.Layout p s
+ addressLayout : AddressCalls.Ready s
+ reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ write : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ words : ∃ old, AddressHeader.Words p pass lane slice old s
+ matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩
+ laneLength : s.gpr .r12 = BitVec.ofNat 64 p.laneLen
+ segmentLength : s.gpr .r13 = BitVec.ofNat 64 p.segmentLen
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+theorem of_segment_ready {p : Params} {pass lane slice : Nat} {s : State}
+ (h : SegmentSetup.Ready p pass lane slice s) : Ready p pass lane slice s :=
+ ⟨h.layout, h.addressLayout, h.reads, h.write, h.words, h.matrixWork, h.laneLength, h.segmentLength, h.lanesWord⟩
+
+theorem Ready.segment {p : Params} {pass lane slice : Nat} {s : State}
+ (h : Ready p pass lane slice s) (parameters : FillContext.Parameters p pass lane slice) :
+ SegmentSetup.Ready p pass lane slice s :=
+ ⟨parameters, h.layout, h.addressLayout, h.reads, h.write, h.words,
+ h.matrixWork, h.laneLength, h.segmentLength, h.lanesWord⟩
+
+theorem Ready.of_state {p : Params} {pass lane slice newLane newSlice : Nat} {s t : State}
+ (h : Ready p pass lane slice s)
+ (regs : ∀ r ∈ [Reg.rbp, .rsp, .r12, .r13], t.gpr r = s.gpr r)
+ (mem : t.mem = s.mem) (rd : t.rd = s.rd) (wr : t.wr = s.wr)
+ (laneWord : t.gpr .rbx = BitVec.ofNat 64 newLane) (sliceWord : t.gpr .r14 = BitVec.ofNat 64 newSlice) :
+ Ready p pass newLane newSlice t := by
+ have bp := regs .rbp (by simp)
+ have sp := regs .rsp (by simp)
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [mem, bp]
+ refine ⟨h.layout.of_preserved bp sp base work rd wr, ?_, ?_, ?_, ?_, ?_,
+ (regs .r12 (by simp)).trans h.laneLength, (regs .r13 (by simp)).trans h.segmentLength, ?_⟩
+ · constructor
+ · rw [rd, wr, bp]; exact h.addressLayout.frameRead
+ · rw [wr, work]; exact h.addressLayout.workWrite
+ · rw [bp, work]; exact h.addressLayout.frameWork
+ · rw [bp, sp]; exact h.addressLayout.frameStack
+ · rw [sp, work]; exact h.addressLayout.stackWork
+ · rw [rd, wr, bp]; exact h.reads
+ · rw [wr, bp]; exact h.write
+ · obtain ⟨old, words⟩ := h.words
+ refine ⟨old, ?_, laneWord, sliceWord, ?_, ?_, ?_, ?_⟩
+ all_goals rw [mem, bp]
+ · exact words.passWord
+ · exact words.blocksWord
+ · exact words.passesWord
+ · exact words.variantWord
+ · exact words.counterWord
+ · rw [base, work]; exact h.matrixWork
+ · rw [mem, bp]; exact h.lanesWord
+
+theorem of_lanes_finished {p : Params} {pass slice : Nat} {s t : State} {state : FillState}
+ (h : FillLanes.Finished s t p pass slice state) : Ready p pass p.lanes slice t := by
+ obtain ⟨lane, a, _, ready, keeps⟩ := h.header
+ obtain ⟨old, words⟩ := ready.words
+ apply (of_segment_ready ready).of_state _ keeps.mem keeps.rd keeps.wr h.laneWord
+ ((keeps.regs .r14 (by decide)).trans words.sliceWord)
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide)
+
+end VG.Proof.Argon2.X86_64.FillHeader
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIndex.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIndex.lean
new file mode 100644
index 000000000..8d4c39437
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIndex.lean
@@ -0,0 +1,81 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSegment
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlock
+
+/-! Public segment-index advancement retains the filling and cache allocations. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSegment
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillSegment
+
+theorem advance_ok (s : State) : WP isa (.block advance) s fun t =>
+ t.gpr .r15 = s.gpr .r15 + 1 ∧
+ t.cf = decide ((s.gpr .r15 + 1).toNat < (s.gpr .r13).toNat) ∧
+ Divide.Keeps [.r15] s t := by
+ apply WP.of_runBlock
+ simp only [advance, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, RegUpd.cf_arithFlags,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ reduceCtorEq, ite_true, ite_false, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem advance_nat_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : FillKernel.Ready p pass lane slice index s) : WP isa (.block advance) s fun t =>
+ t.gpr .r15 = BitVec.ofNat 64 (index + 1) ∧
+ t.cf = decide (index + 1 < p.segmentLen) ∧ Divide.Keeps [.r15] s t := by
+ refine (advance_ok s).mono ?_
+ rintro t ⟨value, flag, keeps⟩
+ have added : s.gpr .r15 + 1 = BitVec.ofNat 64 (index + 1) := by
+ rw [h.position.index, BitVec.ofNat_add]; rfl
+ have endBound : p.segmentLen < 2 ^ 64 := Nat.lt_of_le_of_lt h.bounds.segment_le_lane
+ (Nat.lt_trans h.bounds.laneLength_bound (by decide))
+ have indexBound := h.bounds.indexBound
+ refine ⟨value.trans added, ?_, keeps⟩
+ rw [flag, added, h.position.segmentLength,
+ ReferenceMap.word_nat (index + 1) (by omega), ReferenceMap.word_nat p.segmentLen endBound]
+
+theorem next_ready {p : Params} {pass lane slice index old : Nat} {s t : State}
+ (h : RandomSource.Ready p pass lane slice index old s)
+ (k : Divide.Keeps [.r15] s t) (value : t.gpr .r15 = BitVec.ofNat 64 (index + 1))
+ (active : index + 1 < p.segmentLen) : RandomSource.Ready p pass lane slice (index + 1) old t := by
+ have bp := k.regs .rbp (by decide)
+ have sp := k.regs .rsp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [k.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [k.mem, bp]
+ have kernelWork : FillKernel.work t = FillKernel.work s := work
+ refine ⟨?_, h.cache.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact k.regs _ (by decide)) k.mem k.rd k.wr, ?_⟩
+ · refine ⟨?_, ?_, ?_, ?_, ?_⟩
+ · constructor
+ · rw [k.rd, k.wr, bp]; exact h.filling.layout.frameRead
+ · rw [k.wr, bp]; exact h.filling.layout.frameWrite
+ · rw [base, k.wr]; exact h.filling.layout.matrixWrite
+ · rw [kernelWork, k.wr]; exact h.filling.layout.workWrite
+ · rw [base, kernelWork]; exact h.filling.layout.matrixWork
+ · rw [base, bp]; exact h.filling.layout.matrixFrame
+ · rw [base, sp]; exact h.filling.layout.matrixStack
+ · rw [bp, kernelWork]; exact h.filling.layout.frameWork
+ · rw [bp, sp]; exact h.filling.layout.frameStack
+ · rw [sp, kernelWork]; exact h.filling.layout.stackWork
+ · have bounds := h.filling.bounds
+ refine ⟨bounds.lanesPositive, bounds.lanesBound, bounds.memoryMinimum, bounds.memoryBound,
+ bounds.passBound, bounds.laneBound, bounds.sliceBound, active, ?_⟩
+ rcases bounds.active with hp | hs | hi
+ · exact Or.inl hp
+ · exact Or.inr (Or.inl hs)
+ · exact Or.inr (Or.inr (by omega))
+ · exact ⟨(k.regs .rbx (by decide)).trans h.filling.position.current,
+ (k.regs .r12 (by decide)).trans h.filling.position.laneLength,
+ (k.regs .r13 (by decide)).trans h.filling.position.segmentLength,
+ (k.regs .r14 (by decide)).trans h.filling.position.slice, value⟩
+ · rw [k.mem, bp]; exact h.filling.passWord
+ · rw [k.mem, bp]; exact h.filling.lanesWord
+ · rw [base, work]; exact h.matrixWork
+
+end VG.Proof.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIteration.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIteration.lean
new file mode 100644
index 000000000..bb145ae96
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIteration.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationPrepare
+
+/-! One complete filling pass against the reviewed specification. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIteration
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem code_ok (s : State) (p : Params) (pass : Nat) (h : Ready p pass s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillIteration.code s (FillSlices.Finished s · p pass (fillPass p state pass)) := by
+ unfold Impl.Argon2.X86_64.FillIteration.code
+ refine WP.seq ((setup_ok s p pass h).mono ?_)
+ intro a prepared
+ have bp := prepared.keeps.regs .rbp (by decide)
+ have base : FillKernel.matrix a = FillKernel.matrix s := by unfold FillKernel.matrix; rw [prepared.keeps.mem, bp]
+ have work : AddressCalls.work a = AddressCalls.work s := by unfold AddressCalls.work; rw [prepared.keeps.mem, bp]
+ have representedA : Proof.Argon2.Represents a.mem (FillKernel.matrix a) p.blocks state.memory := by
+ rw [prepared.keeps.mem, base]; exact represented
+ refine (FillSlices.pass_ok a p pass prepared.ready state representedA).mono ?_
+ intro t finished
+ refine ⟨finished.represented, finished.matrix.trans base, finished.work.trans work, finished.header,
+ finished.rd.trans prepared.keeps.rd, finished.wr.trans prepared.keeps.wr, ?_,
+ finished.mxcsr.trans prepared.keeps.mxcsr, ?_⟩
+ · have frame := finished.frame
+ rw [FillBlock.writes, base, work, prepared.keeps.regs .rsp (by decide), bp, prepared.keeps.mem] at frame
+ exact frame
+ · intro r hr bx sl ix
+ have ne : r ∉ [Reg.r14] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact sl
+ exact (finished.regs r hr bx sl ix).trans (prepared.keeps.regs r ne)
+
+end VG.Proof.Argon2.X86_64.FillIteration
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationCT.lean
new file mode 100644
index 000000000..10abf5107
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationCT.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIteration
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlicesCT
+
+/-! Pass setup retains public pointers and exposes only the reviewed pass log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIteration
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ left : Ready p pass s
+ right : Ready p pass t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (fillPass p leftState pass).indices = (fillPass p rightState pass).indices
+
+theorem setup_trace : RelCT isa (fun _ _ : State => True) (.block Impl.Argon2.X86_64.FillIteration.setup) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+
+theorem setup_public_rel (p : Params) (pass : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass leftState rightState) (.block Impl.Argon2.X86_64.FillIteration.setup)
+ (fun s t => FillSlices.NextRelated p pass 0 leftState rightState s t ∧
+ (fillPass p leftState pass).indices = (fillPass p rightState pass).indices) := by
+ have trace := setup_trace.mono (P' := Related p pass leftState rightState)
+ (fun _ _ _ => trivial) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨setup_ok s p pass h.left, setup_ok t p pass h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨⟨ha.ready, hb.ready, ?_, ?_, ?_, ?_, ?_, ?_⟩, hp.indices⟩
+ · rw [ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]; exact hp.bases
+ · rw [ha.keeps.regs .rsp (by decide), hb.keeps.regs .rsp (by decide)]; exact hp.stacks
+ · unfold FillKernel.matrix
+ rw [ha.keeps.mem, hb.keeps.mem, ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]; exact hp.matrices
+ · unfold AddressCalls.work
+ rw [ha.keeps.mem, hb.keeps.mem, ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]; exact hp.work
+ · unfold FillKernel.matrix; rw [ha.keeps.mem, ha.keeps.regs .rbp (by decide)]; exact hp.leftMatrix
+ · unfold FillKernel.matrix; rw [hb.keeps.mem, hb.keeps.regs .rbp (by decide)]; exact hp.rightMatrix
+
+theorem code_rel (p : Params) (pass : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass leftState rightState) Impl.Argon2.X86_64.FillIteration.code (fun _ _ => True) :=
+ (setup_public_rel p pass leftState rightState).seq (FillSlices.pass_rel p pass leftState rightState)
+
+end VG.Proof.Argon2.X86_64.FillIteration
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationPrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationPrepare.lean
new file mode 100644
index 000000000..f7c1378f6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationPrepare.lean
@@ -0,0 +1,31 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillIteration
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlices
+
+/-! Start a pass at slice zero regardless of its incoming lane and slice coordinates. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIteration
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (pass : Nat) (s : State) : Prop where
+ parameters : FillContext.Parameters p pass 0 0
+ header : ∃ lane slice, FillHeader.Ready p pass lane slice s
+
+structure Prepared (s t : State) (p : Params) (pass : Nat) : Prop where
+ ready : FillSlice.Ready p pass 0 t
+ keeps : Divide.Keeps [.r14] s t
+
+theorem setup_ok (s : State) (p : Params) (pass : Nat) (h : Ready p pass s) :
+ WP isa (.block Impl.Argon2.X86_64.FillIteration.setup) s (Prepared s · p pass) := by
+ refine (SegmentSetup.register_ok s .r14 0).mono ?_
+ rintro t ⟨sliceWord, keeps⟩
+ obtain ⟨lane, slice, header⟩ := h.header
+ obtain ⟨old, words⟩ := header.words
+ have next : FillHeader.Ready p pass lane 0 t := header.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide))
+ keeps.mem keeps.rd keeps.wr ((keeps.regs .rbx (by decide)).trans words.laneWord) sliceWord
+ exact ⟨⟨h.parameters, lane, next⟩, keeps⟩
+
+end VG.Proof.Argon2.X86_64.FillIteration
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterations.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterations.lean
new file mode 100644
index 000000000..a9b81309b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterations.lean
@@ -0,0 +1,73 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsBody
+import VerifiedGarbage.Proof.Argon2.Iterations
+
+/-! Termination and correctness of every requested filling pass. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Finished (s t : State) (p : Params) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ header : FillHeader.Ready p p.passes p.lanes 4 t
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r14 → r ≠ .r15 → t.gpr r = s.gpr r
+
+theorem Done.finished {s t : State} {p : Params} {pass : Nat} {state : FillState}
+ (h : Done s t p pass state) (last : pass + 1 = p.passes) : Finished s t p (fillPass p state pass) :=
+ ⟨h.represented, h.matrix, h.work, last ▸ h.header, h.rd, h.wr, h.frame, h.mxcsr, h.regs⟩
+
+theorem Finished.prepend {s a t : State} {p : Params} {pass : Nat} {state finalState : FillState}
+ (first : Done s a p pass state) (rest : Finished a t p finalState) : Finished s t p finalState := by
+ refine ⟨rest.represented, rest.matrix.trans first.matrix, rest.work.trans first.work, rest.header,
+ rest.rd.trans first.rd, rest.wr.trans first.wr, ?_, rest.mxcsr.trans first.mxcsr, ?_⟩
+ · have frame := rest.frame
+ rw [writes, first.matrix, first.work,
+ first.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide),
+ first.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)] at frame
+ exact first.frame.trans frame
+ · intro r hr bx sl ix; exact (rest.regs r hr bx sl ix).trans (first.regs r hr bx sl ix)
+
+theorem loop_ok (count : Nat) (s : State) (p : Params) (pass : Nat) (h : Ready p pass s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (positive : 0 < count) (endPass : pass + count = p.passes) :
+ WP isa Impl.Argon2.X86_64.FillIterations.loop s (Finished s · p (Proof.Argon2.iterations p pass count state)) := by
+ induction count generalizing s pass state with
+ | zero => omega
+ | succ n ih =>
+ obtain ⟨trace, a, run, done⟩ := body_ok s p pass h state represented
+ rw [Proof.Argon2.iterations_succ]
+ cases n with
+ | zero =>
+ have last : pass + 1 = p.passes := endPass
+ refine ⟨_, a, .loopExit run ?_, done.finished last⟩
+ simp only [eval, done.cf, last, Nat.lt_irrefl, decide_false]
+ | succ n =>
+ have active : pass + 1 < p.passes := by omega
+ obtain ⟨restTrace, t, restRun, finished⟩ := ih a (pass + 1) (done.next active)
+ (fillPass p state pass) done.represented (by omega) (by omega)
+ refine ⟨_, t, .loopNext run ?_ restRun, finished.prepend done⟩
+ simp only [eval, done.cf, active, decide_true]
+
+theorem Finished.frame_word {s t : State} {p : Params} {pass : Nat} {state : FillState}
+ (ready : Ready p pass s) (done : Finished s t p state)
+ (d : Nat) (bound : d + 8 ≤ 272) (separate : 24 ≤ d) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)]
+ obtain ⟨lane, slice, header⟩ := ready.filling.header
+ have sub : Region.Sub ⟨off (s.gpr .rbp) d, 8⟩ ⟨s.gpr .rbp, 272⟩ := Offset.sub_base _ bound
+ exact done.frame.readW (r := ⟨off (s.gpr .rbp) d, 8⟩) (Region.contains_self _ _) (by
+ intro r hr
+ simp only [writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact header.layout.matrixFrame.symm.sub_left sub
+ · exact header.addressLayout.frameWork.sub_left sub
+ · exact header.addressLayout.frameStack.sub_left sub
+ · simpa only [off, BitVec.add_zero] using Offset.disjoint (d := d) (n := 8) (e := 0) (k := 24) (s.gpr .rbp) (Or.inr (by omega)) (by omega) (by decide)) (by decide)
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBody.lean
new file mode 100644
index 000000000..85450bb9b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBody.lean
@@ -0,0 +1,65 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsFrame
+
+/-! A complete pass retains the matrix and advances its public iteration counter. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (pass : Nat) (s : State) : Prop where
+ filling : FillIteration.Ready p pass s
+ passesBound : p.passes < 2 ^ 32
+ passWrite : InRegions s.wr (off (s.gpr .rbp) 0) 8
+
+structure Done (s t : State) (p : Params) (pass : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks (fillPass p state pass).memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ header : FillHeader.Ready p (pass + 1) p.lanes 4 t
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r14 → r ≠ .r15 → t.gpr r = s.gpr r
+ cf : t.cf = decide (pass + 1 < p.passes)
+ next : pass + 1 < p.passes → Ready p (pass + 1) t
+
+theorem body_ok (s : State) (p : Params) (pass : Nat) (h : Ready p pass s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillIterations.body s (Done s · p pass state) := by
+ unfold Impl.Argon2.X86_64.FillIterations.body
+ refine WP.seq ((FillIteration.code_ok s p pass h.filling state represented).mono ?_)
+ intro a filled
+ have write : InRegions a.wr (off (a.gpr .rbp) 0) 8 := by
+ rw [filled.wr, filled.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)]
+ exact h.passWrite
+ refine (advance_ok a (filled.header.reads 0 (by simp)) write (filled.header.reads 72 (by simp))).mono ?_
+ intro t saved
+ have header := saved.header filled.header
+ obtain ⟨old, words⟩ := filled.header.words
+ have base : FillKernel.matrix t = FillKernel.matrix a := saved.read 232 (by decide) (by decide)
+ have work : AddressCalls.work t = AddressCalls.work a := saved.read 248 (by decide) (by decide)
+ have passBound := h.filling.parameters.passBound
+ refine ⟨saved.represents filled.header _ filled.represented, base.trans filled.matrix,
+ work.trans filled.work, header, saved.rd.trans filled.rd, saved.wr.trans filled.wr,
+ ?_, saved.mxcsr.trans filled.mxcsr, ?_, ?_, ?_⟩
+ · have lastFrame := saved.outer_frame (p := p)
+ rw [writes, filled.matrix, filled.work,
+ filled.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide),
+ filled.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)] at lastFrame
+ exact (filling_frame filled.frame).trans lastFrame
+ · intro r hr bx sl ix
+ have ne : r ≠ .rax := by
+ 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 (saved.regs r ne).trans (filled.regs r hr bx sl ix)
+ · have added : (BitVec.ofNat 64 pass + 1 : Addr) = BitVec.ofNat 64 (pass + 1) := by
+ rw [BitVec.ofNat_add]; rfl
+ rw [saved.cf, words.passWord, words.passesWord, added, ReferenceMap.word_nat (pass + 1) (by omega),
+ ReferenceMap.word_nat p.passes (Nat.lt_trans h.passesBound (by decide))]
+ · intro active
+ refine ⟨⟨{ h.filling.parameters with passBound := Nat.lt_trans active h.passesBound }, p.lanes, 4, header⟩,
+ h.passesBound, ?_⟩
+ rw [saved.wr, saved.regs .rbp (by decide)]; exact write
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBodyCT.lean
new file mode 100644
index 000000000..1c9d6454d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsBodyCT.lean
@@ -0,0 +1,62 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsBody
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationCT
+
+/-! Iteration advances its public pass counter and retains the reviewed filling log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillIterations
+
+theorem advance_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp) advance (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+structure NextRelated (p : Params) (pass : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ left : Ready p pass s
+ right : Ready p pass t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+
+theorem body_rel (p : Params) (pass : Nat) (leftState rightState : FillState) :
+ RelCT isa (fun s t => NextRelated p pass leftState rightState s t ∧
+ (fillPass p leftState pass).indices = (fillPass p rightState pass).indices) body
+ (fun s t => s.cf = t.cf ∧ (pass + 1 < p.passes → NextRelated p (pass + 1)
+ (fillPass p leftState pass)
+ (fillPass p rightState pass) s t)) := by
+ intro s t ts tt a b hp ea eb
+ obtain ⟨hp, indices⟩ := hp
+ have related : FillIteration.Related p pass leftState rightState s t :=
+ ⟨hp.left.filling, hp.right.filling, hp.bases, hp.stacks, hp.matrices, hp.work, hp.leftMatrix, hp.rightMatrix, indices⟩
+ cases ea with
+ | seq segmentA advanceA =>
+ cases eb with
+ | seq segmentB advanceB =>
+ obtain ⟨segmentTrace, _⟩ := FillIteration.code_rel p pass leftState rightState
+ _ _ _ _ _ _ related segmentA segmentB
+ obtain ⟨_, sa, runA, filledA⟩ := FillIteration.code_ok s p pass hp.left.filling leftState hp.leftMatrix
+ obtain ⟨_, sb, runB, filledB⟩ := FillIteration.code_ok t p pass hp.right.filling rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det segmentA runA
+ obtain ⟨_, rfl⟩ := Exec.det segmentB runB
+ have bases := (filledA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.bases.trans (filledB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm)
+ obtain ⟨advancedTrace, _⟩ := advance_rel _ _ _ _ _ _ bases advanceA advanceB
+ obtain ⟨_, a', runA, doneA⟩ := body_ok s p pass hp.left leftState hp.leftMatrix
+ obtain ⟨_, b', runB, doneB⟩ := body_ok t p pass hp.right rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det (.seq segmentA advanceA) runA
+ obtain ⟨_, rfl⟩ := Exec.det (.seq segmentB advanceB) runB
+ refine ⟨by rw [segmentTrace, advancedTrace], doneA.cf.trans doneB.cf.symm, ?_⟩
+ intro active
+ refine ⟨doneA.next active, doneB.next active, ?_, ?_,
+ doneA.matrix.trans (hp.matrices.trans doneB.matrix.symm),
+ doneA.work.trans (hp.work.trans doneB.work.symm), doneA.represented, doneB.represented⟩
+ · exact (doneA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm)
+ · exact (doneA.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.stacks.trans (doneB.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsCT.lean
new file mode 100644
index 000000000..448a7f9ba
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsCT.lean
@@ -0,0 +1,51 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterations
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsBodyCT
+import VerifiedGarbage.Proof.Argon2.IterationsIndices
+
+/-! The pass loop exposes only the complete filling reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass count : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ ready : NextRelated p pass leftState rightState s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.iterations p pass count leftState).indices =
+ (Proof.Argon2.iterations p pass count rightState).indices
+
+theorem loop_rel (p : Params) (pass count : Nat) (leftState rightState : FillState)
+ (positive : 0 < count) (endPass : pass + count = p.passes) :
+ RelCT isa (Related p pass count leftState rightState) Impl.Argon2.X86_64.FillIterations.loop (fun _ _ => True) := by
+ let I := fun n s t => ∃ (pass : Nat) (leftState rightState : FillState),
+ pass + n = p.passes ∧ 0 < n ∧ Related p pass n leftState rightState s t
+ have steps : ∀ n, RelCT isa (I n) Impl.Argon2.X86_64.FillIterations.body fun s t =>
+ isa.eval .b s = isa.eval .b t ∧ (isa.eval .b s = some false → True) ∧
+ (isa.eval .b s = some true → ∃ m < n, I m s t) := by
+ intro n s t ts tt a b hp ea eb
+ obtain ⟨j, ls, rs, endPass, positive, hp⟩ := hp
+ cases n with
+ | zero => omega
+ | succ n =>
+ have passIndices := Proof.Argon2.iterations_first_pass p j n ls rs
+ hp.ready.left.filling.parameters.segment_bound.1 hp.indices
+ obtain ⟨trace, flags, next⟩ := body_rel p j ls rs _ _ _ _ _ _ ⟨hp.ready, passIndices⟩ ea eb
+ obtain ⟨_, a', runA, done⟩ := body_ok s p j hp.ready.left ls hp.leftMatrix
+ obtain ⟨_, rfl⟩ := Exec.det ea runA
+ refine ⟨trace, ?_, fun _ => trivial, ?_⟩
+ · simp only [eval, flags]
+ · intro taken
+ have active : j + 1 < p.passes := by
+ simp only [eval, done.cf, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ have ready := next active
+ have indices := hp.indices
+ rw [Proof.Argon2.iterations_succ, Proof.Argon2.iterations_succ] at indices
+ exact ⟨n, by omega, j + 1, fillPass p ls j,
+ fillPass p rs j, by omega, by omega, ready, ready.leftMatrix, ready.rightMatrix, indices⟩
+ exact (RelCT.loop I steps count).mono
+ (fun _ _ h => ⟨pass, leftState, rightState, endPass, positive, h⟩) (fun _ _ h => h)
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsFrame.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsFrame.lean
new file mode 100644
index 000000000..59ec3e8f4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillIterationsFrame.lean
@@ -0,0 +1,47 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPassSave
+
+/-! The outer pass loop also writes the public pass word at frame offset zero. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def writes (s : State) (p : Params) : List Region :=
+ [⟨FillKernel.matrix s, p.blocks * 1024⟩, ⟨AddressCalls.work s, 8192⟩,
+ below (s.gpr .rsp) 8, ⟨s.gpr .rbp, 24⟩]
+
+theorem filling_frame {s t : State} {p : Params} (h : Frame (FillBlock.writes s p) s.mem t.mem) :
+ Frame (writes s p) s.mem t.mem := by
+ apply h.sub
+ intro r hr
+ simp only [FillBlock.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨⟨s.gpr .rbp, 24⟩, by simp [writes], Offset.sub_base _ (by decide)⟩
+
+theorem Saved.outer_frame {s t : State} {p : Params} (h : Saved s t) : Frame (writes s p) s.mem t.mem := by
+ apply h.frame.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨s.gpr .rbp, 24⟩, by simp [writes], Offset.sub_base _ (by decide)⟩
+
+theorem Saved.represents {s t : State} {p : Params} {pass lane slice : Nat}
+ (h : Saved s t) (header : FillHeader.Ready p pass lane slice s) (blocks : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks blocks) :
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks blocks := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := h.read 232 (by decide) (by decide)
+ rw [base]
+ refine ⟨represented.size, ?_⟩
+ intro k hk
+ apply Eq.trans _ (represented.block k hk)
+ apply FillCompress.block_frame h.frame
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact (header.layout.matrixFrame.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)).sub_right
+ (Offset.sub_base _ (by decide))
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernel.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernel.lean
new file mode 100644
index 000000000..8f271cd07
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernel.lean
@@ -0,0 +1,67 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelPrepare
+
+/-! Complete active-cell update from a random word and the matrix allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def writes (s : State) (p : Params) (lane slice index : Nat) : List Region :=
+ [⟨current s p lane slice index, 1024⟩, ⟨work s, 5120⟩,
+ below (s.gpr .rsp) 8, ⟨off (s.gpr .rbp) 16, 8⟩]
+
+structure Done (s t : State) (p : Params) (pass lane slice index : Nat) : Prop where
+ block : blockAt t.mem (current s p lane slice index) =
+ let next := Spec.Argon2.compress (blockAt s.mem (previous s p lane slice index))
+ (blockAt s.mem (referenced s p pass lane slice index))
+ if pass = 0 then next else xorBlock next (blockAt s.mem (current s p lane slice index))
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p lane slice index) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem code_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : Ready p pass lane slice index s) :
+ WP isa Impl.Argon2.X86_64.FillKernel.code s (Done s · p pass lane slice index) := by
+ unfold Impl.Argon2.X86_64.FillKernel.code
+ refine WP.seq ((prepare_ok s p pass lane slice index h).mono ?_)
+ intro b prepared
+ have keeps := prepared.keeps
+ have cur := prepared.currentPtr
+ have prev := prepared.previousPtr
+ have other := prepared.referencePtr
+ refine (FillCompress.code_mx_ok b prepared.ready).mono ?_
+ rintro t ⟨done, mx⟩
+ have counter : FillCompress.pass b = BitVec.ofNat 64 pass := by
+ unfold FillCompress.pass
+ rw [keeps.mem, keeps.regs .rbp (by decide)]
+ exact h.passWord
+ refine ⟨?_, ?_, done.rd.trans keeps.rd, done.wr.trans keeps.wr, ?_, mx.trans keeps.mxcsr⟩
+ · have block := done.block
+ rw [cur, prev, other, keeps.mem, counter] at block
+ simp only [ReferenceMap.word_zero pass (Nat.lt_trans h.bounds.passBound (by decide))] at block
+ exact block
+ · intro r hr
+ have ne : r ∉ ReferenceMap.changed := by
+ 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 (done.regs r hr).trans (keeps.regs r ne)
+ · have workB : FillCompress.work b = work s := by
+ unfold FillCompress.work work
+ rw [keeps.regs .rbp (by decide), keeps.mem]
+ have frame := done.frame
+ rw [FillCompress.writes, workB, cur, keeps.regs .rsp (by decide), keeps.regs .rbp (by decide), keeps.mem] at frame
+ change Frame [⟨current s p lane slice index, 1024⟩, ⟨work s + 4096, 1024⟩,
+ ⟨work s, 4096⟩, below (s.gpr .rsp) 8, ⟨off (s.gpr .rbp) 16, 8⟩] s.mem t.mem at frame
+ apply frame.sub
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨⟨work s, 5120⟩, by simp [writes], Offset.sub_base _ (by decide)⟩
+ · exact ⟨⟨work s, 5120⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelArgs.lean
new file mode 100644
index 000000000..b2afb6895
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelArgs.lean
@@ -0,0 +1,80 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelLayout
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMap
+
+/-! Reload frame arguments and compose reference mapping with matrix addresses. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem load_ok (s : State) (r : Reg) (d : Nat)
+ (read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8) :
+ WP isa (.block [.mov r (.mem (Impl.Argon2.X86_64.at_ .rbp d))]) s fun t =>
+ t.gpr r = s.mem.readW (off (s.gpr .rbp) d) 64 ∧ Divide.Keeps [r] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, read, Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro q hq
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hq
+ exact ite_eq_right hq
+ all_goals rfl
+
+structure Ready (p : Params) (pass lane slice index : Nat) (s : State) : Prop where
+ layout : Layout p s
+ bounds : ReferenceMap.Bounds p pass lane slice index
+ position : ReferenceMap.Position p lane slice index s
+ passWord : s.mem.readW (off (s.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+structure Mapped (s t : State) (p : Params) (pass lane slice index : Nat) : Prop where
+ selected : t.gpr .r9 = BitVec.ofNat 64 (Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)).1
+ column : t.gpr .rdi = BitVec.ofNat 64 (Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)).2
+ original : t.gpr .r11 = s.gpr .rdi
+ keeps : Divide.Keeps ReferenceMap.changed s t
+
+theorem mapping_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : Ready p pass lane slice index s) :
+ WP isa Impl.Argon2.X86_64.FillKernel.mapping s (Mapped s · p pass lane slice index) := by
+ unfold Impl.Argon2.X86_64.FillKernel.mapping
+ refine WP.seq ((load_ok s .rsi 184 (h.layout.frameRead 184 (by simp))).mono ?_)
+ rintro a ⟨lanes, keeps⟩
+ have k : Divide.Keeps ReferenceMap.changed s a := keeps.mono (by decide)
+ have ready : ReferenceMap.Ready p pass lane slice index a := by
+ refine ⟨h.bounds, h.position.of_keeps k, lanes.trans h.lanesWord, ?_, ?_⟩
+ · rw [k.rd, k.wr, k.regs .rbp (by decide)]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero]
+ using h.layout.frameRead 0 (by simp)
+ · rw [k.mem, k.regs .rbp (by decide)]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero] using h.passWord
+ refine (ReferenceMap.code_spec_ok a p pass lane slice index ready).mono ?_
+ rintro t ⟨lane, column, original, tail⟩
+ rw [keeps.regs .rdi (by decide)] at lane column original
+ exact ⟨lane, column, original, k.trans tail⟩
+
+structure Pointers (s t : State) (p : Params) (lane slice index refLane refColumn : Nat) : Prop where
+ current : t.gpr .r10 = FillPointers.cell (matrix s) p lane (slice * p.segmentLen + index)
+ previous : t.gpr .rdi = FillPointers.cell (matrix s) p lane
+ ((slice * p.segmentLen + index + p.laneLen - 1) % p.laneLen)
+ reference : t.gpr .rsi = FillPointers.cell (matrix s) p refLane refColumn
+ keeps : Divide.Keeps ReferenceMap.changed s t
+
+theorem pointers_ok (s : State) (p : Params) (pass lane slice index refLane refColumn : Nat)
+ (layout : Layout p s) (bounds : ReferenceMap.Bounds p pass lane slice index)
+ (position : ReferenceMap.Position p lane slice index s)
+ (laneWord : s.gpr .r9 = BitVec.ofNat 64 refLane) (columnWord : s.gpr .rdi = BitVec.ofNat 64 refColumn) :
+ WP isa Impl.Argon2.X86_64.FillKernel.pointers s (Pointers s · p lane slice index refLane refColumn) := by
+ unfold Impl.Argon2.X86_64.FillKernel.pointers
+ refine WP.seq ((load_ok s .r8 232 (layout.frameRead 232 (by simp))).mono ?_)
+ rintro a ⟨base, keeps⟩
+ have k : Divide.Keeps ReferenceMap.changed s a := keeps.mono (by decide)
+ have lane' : a.gpr .r9 = BitVec.ofNat 64 refLane := (keeps.regs .r9 (by decide)).trans laneWord
+ have col' : a.gpr .rdi = BitVec.ofNat 64 refColumn := (keeps.regs .rdi (by decide)).trans columnWord
+ refine (FillPointers.code_nat_ok a p pass lane slice index refLane refColumn bounds
+ (position.of_keeps k) lane' col').mono ?_
+ rintro t ⟨current, previous, reference, tail⟩
+ rw [base] at current previous reference
+ exact ⟨current, previous, reference, k.trans (tail.mono (by decide))⟩
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelCT.lean
new file mode 100644
index 000000000..e5b41c53d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelCT.lean
@@ -0,0 +1,57 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelMappingCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressCT
+
+/-! Equal permitted references give equal compression and block-update traces. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem prepared_public {p : Params} {pass lane slice index : Nat} {s t a b : State}
+ (h : Related p pass lane slice index s t)
+ (ha : Prepared s a p pass lane slice index) (hb : Prepared t b p pass lane slice index) :
+ FillCompress.CodeRelated a b := by
+ have currentEq : current s p lane slice index = current t p lane slice index := by
+ unfold current; rw [h.matrices]
+ have previousEq : previous s p lane slice index = previous t p lane slice index := by
+ unfold previous; rw [h.matrices]
+ have referenceEq : referenced s p pass lane slice index = referenced t p pass lane slice index := by
+ unfold referenced; rw [h.references, h.matrices]
+ have workA : FillCompress.work a = work s := by
+ unfold FillCompress.work work; rw [ha.keeps.regs .rbp (by decide), ha.keeps.mem]
+ have workB : FillCompress.work b = work t := by
+ unfold FillCompress.work work; rw [hb.keeps.regs .rbp (by decide), hb.keeps.mem]
+ have passA : FillCompress.pass a = BitVec.ofNat 64 pass := by
+ unfold FillCompress.pass
+ rw [ha.keeps.regs .rbp (by decide), ha.keeps.mem]
+ exact h.left.passWord
+ have passB : FillCompress.pass b = BitVec.ofNat 64 pass := by
+ unfold FillCompress.pass
+ rw [hb.keeps.regs .rbp (by decide), hb.keeps.mem]
+ exact h.right.passWord
+ refine ⟨ha.ready, hb.ready, ?_, workA.trans (h.scratch.trans workB.symm), passA.trans passB.symm⟩
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl
+ · exact ha.previousPtr.trans (previousEq.trans hb.previousPtr.symm)
+ · exact ha.referencePtr.trans (referenceEq.trans hb.referencePtr.symm)
+ · exact ha.currentPtr.trans (currentEq.trans hb.currentPtr.symm)
+ · exact (ha.keeps.regs .rsp (by decide)).trans (h.stacks.trans (hb.keeps.regs .rsp (by decide)).symm)
+ · exact (ha.keeps.regs .rbp (by decide)).trans (h.bases.trans (hb.keeps.regs .rbp (by decide)).symm)
+
+theorem prepare_public_rel (p : Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) Impl.Argon2.X86_64.FillKernel.prepare
+ FillCompress.CodeRelated := by
+ have trace := (mapping_public_rel p pass lane slice index).seq (pointers_trace p lane slice index)
+ have full := trace.wpDep (fun s t h =>
+ ⟨prepare_ok s p pass lane slice index h.left, prepare_ok t p pass lane slice index h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact prepared_public hp ha hb
+
+theorem code_rel (p : Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) Impl.Argon2.X86_64.FillKernel.code
+ (fun _ _ => True) := (prepare_public_rel p pass lane slice index).seq FillCompress.code_rel
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelInvariant.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelInvariant.lean
new file mode 100644
index 000000000..fe0abd23b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelInvariant.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernel
+
+/-! Each active-cell update retains the frame and matrix allocation invariant. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Done.frame_word {s t : State} {p : Params} {pass lane slice index : Nat}
+ (h : Ready p pass lane slice index s) (done : Done s t p pass lane slice index)
+ (d : Nat) (bound : d + 8 ≤ 272) (separate : d + 8 ≤ 16 ∨ 24 ≤ d) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.regs .rbp (by simp [calleeSaved])]
+ have sub : Region.Sub ⟨off (s.gpr .rbp) d, 8⟩ ⟨s.gpr .rbp, 272⟩ := Offset.sub_base _ bound
+ have currentSub : Region.Sub ⟨current s p lane slice index, 1024⟩ ⟨matrix s, p.blocks * 1024⟩ :=
+ cell_sub p _ h.bounds.lanesPositive h.bounds.laneBound
+ (Proof.Argon2.column_lt p h.bounds.lanesPositive h.bounds.sliceBound h.bounds.indexBound)
+ exact done.frame.readW (r := ⟨off (s.gpr .rbp) d, 8⟩) (Region.contains_self _ _) (by
+ intro r hr
+ simp only [writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact ((h.layout.matrixFrame.sub_left currentSub).symm).sub_left sub
+ · exact h.layout.frameWork.sub_left sub
+ · exact h.layout.frameStack.sub_left sub
+ · exact Offset.disjoint _ separate (by omega) (by decide)) (by decide)
+
+theorem Done.retains {s t : State} {p : Params} {pass lane slice index : Nat}
+ (h : Ready p pass lane slice index s) (done : Done s t p pass lane slice index) :
+ Ready p pass lane slice index t := by
+ have bp := done.regs .rbp (by simp [calleeSaved])
+ have sp := done.regs .rsp (by simp [calleeSaved])
+ have matrix' : matrix t = matrix s := done.frame_word h 232 (by decide) (by decide)
+ have work' : work t = work s := done.frame_word h 248 (by decide) (by decide)
+ refine ⟨?_, h.bounds, ?_, (done.frame_word h 0 (by decide) (by decide)).trans h.passWord,
+ (done.frame_word h 184 (by decide) (by decide)).trans h.lanesWord⟩
+ · constructor
+ · rw [done.rd, done.wr, bp]; exact h.layout.frameRead
+ · rw [done.wr, bp]; exact h.layout.frameWrite
+ · rw [matrix', done.wr]; exact h.layout.matrixWrite
+ · rw [work', done.wr]; exact h.layout.workWrite
+ · rw [matrix', work']; exact h.layout.matrixWork
+ · rw [matrix', bp]; exact h.layout.matrixFrame
+ · rw [matrix', sp]; exact h.layout.matrixStack
+ · rw [bp, work']; exact h.layout.frameWork
+ · rw [bp, sp]; exact h.layout.frameStack
+ · rw [sp, work']; exact h.layout.stackWork
+ · exact ⟨(done.regs .rbx (by simp [calleeSaved])).trans h.position.current,
+ (done.regs .r12 (by simp [calleeSaved])).trans h.position.laneLength,
+ (done.regs .r13 (by simp [calleeSaved])).trans h.position.segmentLength,
+ (done.regs .r14 (by simp [calleeSaved])).trans h.position.slice,
+ (done.regs .r15 (by simp [calleeSaved])).trans h.position.index⟩
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelLayout.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelLayout.lean
new file mode 100644
index 000000000..f822a6935
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelLayout.lean
@@ -0,0 +1,101 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointersNat
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompress
+import VerifiedGarbage.Impl.Argon2.X86_64.FillKernel
+
+/-! One allocation invariant covers all matrix cells used by the filling step. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def matrix (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 232) 64
+
+def work (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 248) 64
+
+structure Layout (p : Params) (s : State) : Prop where
+ frameRead : ∀ d ∈ [0, 16, 184, 232, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ frameWrite : InRegions s.wr (off (s.gpr .rbp) 16) 8
+ matrixWrite : Covers [⟨matrix s, p.blocks * 1024⟩] s.wr
+ workWrite : Covers [⟨work s, 5120⟩] s.wr
+ matrixWork : (⟨matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨work s, 5120⟩
+ matrixFrame : (⟨matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩
+ matrixStack : (⟨matrix s, p.blocks * 1024⟩ : Region).Disjoint (below (s.gpr .rsp) 8)
+ frameWork : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint ⟨work s, 5120⟩
+ frameStack : (⟨s.gpr .rbp, 272⟩ : Region).Disjoint (below (s.gpr .rsp) 8)
+ stackWork : (below (s.gpr .rsp) 8).Disjoint ⟨work s, 5120⟩
+
+theorem Layout.of_keeps {p : Params} {s t : State} (h : Layout p s)
+ (k : Divide.Keeps ReferenceMap.changed s t) : Layout p t := by
+ have bp := k.regs .rbp (by decide)
+ have sp := k.regs .rsp (by decide)
+ have matrix' : matrix t = matrix s := by unfold matrix; rw [bp, k.mem]
+ have work' : work t = work s := by unfold work; rw [bp, k.mem]
+ constructor
+ · rw [k.rd, k.wr, bp]; exact h.frameRead
+ · rw [k.wr, bp]; exact h.frameWrite
+ · rw [matrix', k.wr]; exact h.matrixWrite
+ · rw [work', k.wr]; exact h.workWrite
+ · rw [matrix', work']; exact h.matrixWork
+ · rw [matrix', bp]; exact h.matrixFrame
+ · rw [matrix', sp]; exact h.matrixStack
+ · rw [bp, work']; exact h.frameWork
+ · rw [bp, sp]; exact h.frameStack
+ · rw [sp, work']; exact h.stackWork
+
+theorem cell_sub (p : Params) (base : Addr) (positive : 0 < p.lanes) {lane column : Nat}
+ (hl : lane < p.lanes) (hc : column < p.laneLen) :
+ Region.Sub ⟨FillPointers.cell base p lane column, 1024⟩ ⟨base, p.blocks * 1024⟩ :=
+ Offset.sub_base base (Proof.Argon2.cell_bytes p positive hl hc)
+
+theorem Layout.cell_cover {p : Params} {s : State} (h : Layout p s) (positive : 0 < p.lanes)
+ {lane column : Nat} (hl : lane < p.lanes) (hc : column < p.laneLen) :
+ Covers [⟨FillPointers.cell (matrix s) p lane column, 1024⟩] s.wr := by
+ have sub : Covers [⟨FillPointers.cell (matrix s) p lane column, 1024⟩]
+ [⟨matrix s, p.blocks * 1024⟩] := Covers.of_sub (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨matrix s, p.blocks * 1024⟩, by simp, (lane * p.laneLen + column) * 1024,
+ rfl, Proof.Argon2.cell_bytes p positive hl hc⟩)
+ exact fun a n ha => h.matrixWrite a n (sub a n ha)
+
+theorem compress_ready (p : Params) (s : State) (layout : Layout p s)
+ (positive : 0 < p.lanes) (leftLane leftColumn rightLane rightColumn destLane destColumn : Nat)
+ (ll : leftLane < p.lanes) (lc : leftColumn < p.laneLen)
+ (rl : rightLane < p.lanes) (rc : rightColumn < p.laneLen)
+ (dl : destLane < p.lanes) (dc : destColumn < p.laneLen)
+ (left : s.gpr .rdi = FillPointers.cell (matrix s) p leftLane leftColumn)
+ (right : s.gpr .rsi = FillPointers.cell (matrix s) p rightLane rightColumn)
+ (dest : s.gpr .r10 = FillPointers.cell (matrix s) p destLane destColumn) : FillCompress.Ready s := by
+ have leftSub := cell_sub p (matrix s) positive ll lc
+ have rightSub := cell_sub p (matrix s) positive rl rc
+ have destSub := cell_sub p (matrix s) positive dl dc
+ have read (lane column : Nat) (hl : lane < p.lanes) (hc : column < p.laneLen) :
+ Covers [⟨FillPointers.cell (matrix s) p lane column, 1024⟩] (s.rd ++ s.wr) := by
+ intro a n ha
+ obtain ⟨r, hr, hc⟩ := layout.cell_cover positive hl hc a n ha
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ constructor
+ · intro d hd
+ exact layout.frameRead d (by
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hd
+ rcases hd with rfl | rfl | rfl <;> simp)
+ · exact layout.frameWrite
+ · rw [left]; exact read leftLane leftColumn ll lc
+ · rw [right]; exact read rightLane rightColumn rl rc
+ · rw [dest]; exact layout.cell_cover positive dl dc
+ · exact layout.workWrite
+ · rw [left]; exact layout.matrixWork.sub_left leftSub
+ · rw [right]; exact layout.matrixWork.sub_left rightSub
+ · rw [dest]; exact layout.matrixWork.sub_left destSub
+ · exact layout.frameWork
+ · rw [left]; exact layout.matrixFrame.sub_left leftSub
+ · rw [right]; exact layout.matrixFrame.sub_left rightSub
+ · rw [dest]; exact layout.matrixFrame.sub_left destSub
+ · rw [left]; exact (layout.matrixStack.sub_left leftSub).symm
+ · rw [right]; exact (layout.matrixStack.sub_left rightSub).symm
+ · exact layout.stackWork
+ · rw [dest]; exact layout.matrixStack.sub_left destSub
+ · exact layout.frameStack
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMappingCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMappingCT.lean
new file mode 100644
index 000000000..2d48c8db4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMappingCT.lean
@@ -0,0 +1,114 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelPrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointersCT
+
+/-! Reference mapping exposes no more than the permitted reference coordinates. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ left : Ready p pass lane slice index s
+ right : Ready p pass lane slice index t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : matrix s = matrix t
+ scratch : work s = work t
+ references : Spec.Argon2.reference p pass lane slice index (s.gpr .rdi) =
+ Spec.Argon2.reference p pass lane slice index (t.gpr .rdi)
+
+structure PointerRelated (p : Params) (lane slice index : Nat) (s t : State) : Prop where
+ left : Layout p s
+ right : Layout p t
+ leftPosition : ReferenceMap.Position p lane slice index s
+ rightPosition : ReferenceMap.Position p lane slice index t
+ bases : s.gpr .rbp = t.gpr .rbp
+ matrices : matrix s = matrix t
+
+theorem lanes_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block Impl.Argon2.X86_64.FillKernel.lanes) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem lanes_ready (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : Ready p pass lane slice index s) :
+ WP isa (.block Impl.Argon2.X86_64.FillKernel.lanes) s fun t =>
+ ReferenceMap.Ready p pass lane slice index t ∧ Divide.Keeps ReferenceMap.changed s t := by
+ refine (load_ok s .rsi 184 (h.layout.frameRead 184 (by simp))).mono ?_
+ rintro t ⟨lanes, keeps⟩
+ have k : Divide.Keeps ReferenceMap.changed s t := keeps.mono (by decide)
+ refine ⟨⟨h.bounds, h.position.of_keeps k, lanes.trans h.lanesWord, ?_, ?_⟩, k⟩
+ · rw [k.rd, k.wr, k.regs .rbp (by decide)]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero]
+ using h.layout.frameRead 0 (by simp)
+ · rw [k.mem, k.regs .rbp (by decide)]
+ simpa only [off, show BitVec.ofNat 64 0 = 0#64 from rfl, BitVec.add_zero] using h.passWord
+
+theorem lanes_public_rel (p : Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) (.block Impl.Argon2.X86_64.FillKernel.lanes)
+ (ReferenceMap.Related p pass lane slice index) := by
+ have trace := lanes_rel.mono (P' := Related p pass lane slice index)
+ (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨lanes_ready s p pass lane slice index h.left, lanes_ready t p pass lane slice index h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact ⟨ha.1, hb.1, (ha.2.regs .rbp (by decide)).trans
+ (hp.bases.trans (hb.2.regs .rbp (by decide)).symm)⟩
+
+theorem mapping_public_rel (p : Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) Impl.Argon2.X86_64.FillKernel.mapping
+ (PointerRelated p lane slice index) := by
+ have trace := (lanes_public_rel p pass lane slice index).seq (ReferenceMap.code_rel p pass lane slice index)
+ have full := trace.wpDep (fun s t h =>
+ ⟨mapping_ok s p pass lane slice index h.left, mapping_ok t p pass lane slice index h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨hp.left.layout.of_keeps ha.keeps, hp.right.layout.of_keeps hb.keeps,
+ hp.left.position.of_keeps ha.keeps, hp.right.position.of_keeps hb.keeps, ?_, ?_⟩
+ · exact (ha.keeps.regs .rbp (by decide)).trans (hp.bases.trans (hb.keeps.regs .rbp (by decide)).symm)
+ · unfold matrix
+ rw [ha.keeps.mem, hb.keeps.mem, ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]
+ exact hp.matrices
+
+theorem matrix_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block Impl.Argon2.X86_64.FillKernel.matrix) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+theorem pointers_trace (p : Params) (lane slice index : Nat) :
+ RelCT isa (PointerRelated p lane slice index) Impl.Argon2.X86_64.FillKernel.pointers
+ (fun _ _ => True) := by
+ have trace := matrix_rel.mono (P' := PointerRelated p lane slice index)
+ (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h =>
+ ⟨load_ok s .r8 232 (h.left.frameRead 232 (by simp)),
+ load_ok t .r8 232 (h.right.frameRead 232 (by simp))⟩)
+ have args : RelCT isa (PointerRelated p lane slice index) (.block Impl.Argon2.X86_64.FillKernel.matrix)
+ (fun s t => ∀ r ∈ [Reg.r8, .rbx, .r12, .r13, .r14, .r15], s.gpr r = t.gpr r) :=
+ full.mono (fun _ _ h => h) (by
+ intro a b h r hr
+ obtain ⟨_, s, t, hp, ⟨va, ka⟩, ⟨vb, kb⟩⟩ := h
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl | rfl
+ · exact va.trans (hp.matrices.trans vb.symm)
+ all_goals rw [ka.regs _ (by decide), kb.regs _ (by decide)]
+ · exact hp.leftPosition.current.trans hp.rightPosition.current.symm
+ · exact hp.leftPosition.laneLength.trans hp.rightPosition.laneLength.symm
+ · exact hp.leftPosition.segmentLength.trans hp.rightPosition.segmentLength.symm
+ · exact hp.leftPosition.slice.trans hp.rightPosition.slice.symm
+ · exact hp.leftPosition.index.trans hp.rightPosition.index.symm)
+ exact (args.seq FillPointers.code_rel).mono (fun _ _ h => h) (fun _ _ _ => trivial)
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMatrix.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMatrix.lean
new file mode 100644
index 000000000..002908e3f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelMatrix.lean
@@ -0,0 +1,60 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernel
+import VerifiedGarbage.Proof.Argon2.Matrix
+
+/-! The filling step updates exactly one cell of the specification's block array. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def currentIndex (p : Params) (lane slice index : Nat) : Nat :=
+ lane * p.laneLen + currentColumn p slice index
+
+def previousIndex (p : Params) (lane slice index : Nat) : Nat :=
+ lane * p.laneLen + previousColumn p slice index
+
+def referenceIndex (s : State) (p : Params) (pass lane slice index : Nat) : Nat :=
+ let ref := Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)
+ ref.1 * p.laneLen + ref.2
+
+def nextBlock (s : State) (p : Params) (pass lane slice index : Nat) (blocks : Array Block) : Block :=
+ let next := Spec.Argon2.compress (blocks[previousIndex p lane slice index]?.getD zeroBlock)
+ (blocks[referenceIndex s p pass lane slice index]?.getD zeroBlock)
+ if pass = 0 then next else xorBlock next (blocks[currentIndex p lane slice index]?.getD zeroBlock)
+
+theorem Done.represents {s t : State} {p : Params} {pass lane slice index : Nat}
+ (ready : Ready p pass lane slice index s) (done : Done s t p pass lane slice index)
+ (blocks : Array Block) (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks blocks) :
+ Proof.Argon2.Represents t.mem (matrix s) p.blocks
+ (blocks.set! (currentIndex p lane slice index) (nextBlock s p pass lane slice index blocks)) := by
+ have currentBound := Proof.Argon2.current_cell_lt p ready.bounds.lanesPositive
+ ready.bounds.laneBound ready.bounds.sliceBound ready.bounds.indexBound
+ have previousBound := Proof.Argon2.previous_cell_lt p ready.bounds.lanesPositive
+ ready.bounds.memoryMinimum ready.bounds.laneBound (column := currentColumn p slice index)
+ have referenceBound := Proof.Argon2.reference_cell_lt p ready.bounds.lanesPositive
+ ready.bounds.memoryMinimum pass lane slice index (s.gpr .rdi) ready.bounds.laneBound
+ apply represented.update (currentIndex p lane slice index) currentBound (nextBlock s p pass lane slice index blocks)
+ · have block := done.block
+ change blockAt t.mem (Proof.Argon2.matrixCell (matrix s) (currentIndex p lane slice index)) = _ at block
+ have prev := represented.block (previousIndex p lane slice index) previousBound
+ have other := represented.block (referenceIndex s p pass lane slice index) referenceBound
+ have old := represented.block (currentIndex p lane slice index) currentBound
+ change blockAt s.mem (previous s p lane slice index) = _ at prev
+ change blockAt s.mem (referenced s p pass lane slice index) = _ at other
+ change blockAt s.mem (current s p lane slice index) = _ at old
+ rw [prev, other, old] at block
+ exact block
+ · intro j hj different
+ apply FillCompress.block_frame done.frame
+ intro r hr
+ simp only [writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · have blocksBound := Nat.lt_of_le_of_lt (Proof.Argon2.blocks_le_memory p) ready.bounds.memoryBound
+ exact Proof.Argon2.matrixCell_disjoint _ p.blocks j (currentIndex p lane slice index)
+ (Nat.lt_trans (Nat.mul_lt_mul_of_pos_right blocksBound (by decide)) (by decide)) hj currentBound different
+ · exact ready.layout.matrixWork.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hj)
+ · exact ready.layout.matrixStack.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hj)
+ · exact (ready.layout.matrixFrame.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hj)).sub_right
+ (Offset.sub_base _ (by decide))
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelPrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelPrepare.lean
new file mode 100644
index 000000000..2285bbed3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelPrepare.lean
@@ -0,0 +1,67 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelArgs
+
+/-! Complete active-cell update from a random word and the matrix allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def currentColumn (p : Params) (slice index : Nat) : Nat := slice * p.segmentLen + index
+
+def previousColumn (p : Params) (slice index : Nat) : Nat :=
+ (currentColumn p slice index + p.laneLen - 1) % p.laneLen
+
+def current (s : State) (p : Params) (lane slice index : Nat) : Addr :=
+ FillPointers.cell (matrix s) p lane (currentColumn p slice index)
+
+def previous (s : State) (p : Params) (lane slice index : Nat) : Addr :=
+ FillPointers.cell (matrix s) p lane (previousColumn p slice index)
+
+def referenced (s : State) (p : Params) (pass lane slice index : Nat) : Addr :=
+ let ref := Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)
+ FillPointers.cell (matrix s) p ref.1 ref.2
+
+structure Prepared (s t : State) (p : Params) (pass lane slice index : Nat) : Prop where
+ ready : FillCompress.Ready t
+ currentPtr : t.gpr .r10 = current s p lane slice index
+ previousPtr : t.gpr .rdi = previous s p lane slice index
+ referencePtr : t.gpr .rsi = referenced s p pass lane slice index
+ keeps : Divide.Keeps ReferenceMap.changed s t
+
+theorem prepare_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (h : Ready p pass lane slice index s) :
+ WP isa Impl.Argon2.X86_64.FillKernel.prepare s (Prepared s · p pass lane slice index) := by
+ unfold Impl.Argon2.X86_64.FillKernel.prepare
+ refine WP.seq ((mapping_ok s p pass lane slice index h).mono ?_)
+ intro a mapped
+ let ref := Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)
+ refine ((pointers_ok a p pass lane slice index ref.1 ref.2
+ (h.layout.of_keeps mapped.keeps) h.bounds (h.position.of_keeps mapped.keeps)
+ mapped.selected mapped.column).mono ?_)
+ intro b pointers
+ have keeps := mapped.keeps.trans pointers.keeps
+ have matrixA : matrix a = matrix s := by
+ unfold matrix; rw [mapped.keeps.mem, mapped.keeps.regs .rbp (by decide)]
+ have matrixB : matrix b = matrix a := by
+ unfold matrix; rw [pointers.keeps.mem, pointers.keeps.regs .rbp (by decide)]
+ have cur : b.gpr .r10 = current s p lane slice index := by
+ rw [pointers.current, matrixA]; rfl
+ have prev : b.gpr .rdi = previous s p lane slice index := by
+ rw [pointers.previous, matrixA]; rfl
+ have other : b.gpr .rsi = referenced s p pass lane slice index := by
+ rw [pointers.reference, matrixA]; rfl
+ have columnBound := Proof.Argon2.column_lt p h.bounds.lanesPositive h.bounds.sliceBound h.bounds.indexBound
+ have previousBound := Proof.Argon2.previous_column_lt p h.bounds.lanesPositive h.bounds.memoryMinimum
+ (slice * p.segmentLen + index)
+ obtain ⟨refLane, refColumn⟩ := Proof.Argon2.reference_bounds p h.bounds.lanesPositive
+ h.bounds.memoryMinimum pass lane slice index (s.gpr .rdi) h.bounds.laneBound
+ have compressReady : FillCompress.Ready b := by
+ apply compress_ready p b (h.layout.of_keeps keeps) h.bounds.lanesPositive
+ lane (previousColumn p slice index) ref.1 ref.2 lane (currentColumn p slice index)
+ h.bounds.laneBound previousBound refLane refColumn h.bounds.laneBound columnBound
+ · rw [pointers.previous, matrixB]; rfl
+ · rw [pointers.reference, matrixB]
+ · rw [pointers.current, matrixB]; rfl
+ exact ⟨compressReady, cur, prev, other, keeps⟩
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelSpec.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelSpec.lean
new file mode 100644
index 000000000..09daeddab
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelSpec.lean
@@ -0,0 +1,32 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelMatrix
+import VerifiedGarbage.Proof.Argon2.FillStep
+
+/-! Relate the complete assembly step to the reviewed filling-state transition. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem update_spec (s : State) (p : Params) (pass lane slice index : Nat) (state : FillState)
+ (active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index)
+ (random : s.gpr .rdi = Proof.Argon2.FillStep.random p pass lane slice index state.memory) :
+ state.memory.set! (currentIndex p lane slice index) (nextBlock s p pass lane slice index state.memory) =
+ (fillBlock p pass slice lane index state).memory := by
+ rw [Proof.Argon2.FillStep.memory p pass lane slice index state active]
+ unfold nextBlock referenceIndex
+ rw [random]
+ rfl
+
+theorem code_spec_ok (s : State) (p : Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks state.memory)
+ (random : s.gpr .rdi = Proof.Argon2.FillStep.random p pass lane slice index state.memory) :
+ WP isa Impl.Argon2.X86_64.FillKernel.code s fun t => Done s t p pass lane slice index ∧
+ Proof.Argon2.Represents t.mem (matrix s) p.blocks (fillBlock p pass slice lane index state).memory := by
+ refine (code_ok s p pass lane slice index ready).mono ?_
+ intro t done
+ have represented' := done.represents ready state.memory represented
+ rw [update_spec s p pass lane slice index state ready.bounds.active random] at represented'
+ exact ⟨done, represented'⟩
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelStable.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelStable.lean
new file mode 100644
index 000000000..007196a70
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillKernelStable.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillKernelArgs
+
+/-! Register-only helpers retain the filling allocation and position invariants. -/
+
+namespace VG.Proof.Argon2.X86_64.FillKernel
+
+open VG VG.X86_64
+
+theorem Ready.of_keeps {p : Spec.Argon2.Params} {pass lane slice index : Nat} {s t : State}
+ (h : Ready p pass lane slice index s) (k : Divide.Keeps ReferenceMap.changed s t) :
+ Ready p pass lane slice index t := by
+ refine ⟨h.layout.of_keeps k, h.bounds, h.position.of_keeps k, ?_, ?_⟩
+ · rw [k.mem, k.regs .rbp (by decide)]; exact h.passWord
+ · rw [k.mem, k.regs .rbp (by decide)]; exact h.lanesWord
+
+end VG.Proof.Argon2.X86_64.FillKernel
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLaneAdvance.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLaneAdvance.lean
new file mode 100644
index 000000000..b145987f7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLaneAdvance.lean
@@ -0,0 +1,69 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillLanes
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetup
+
+/-! Lane advancement retains the allocation and public segment parameters. -/
+
+namespace VG.Proof.Argon2.X86_64.FillLanes
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillLanes
+
+theorem advance_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 184) 8) :
+ WP isa (.block advance) s fun t => t.gpr .rbx = s.gpr .rbx + 1 ∧
+ t.cf = decide ((s.gpr .rbx + 1).toNat < (s.mem.readW (off (s.gpr .rbp) 184) 64).toNat) ∧
+ Divide.Keeps [.rbx] s t := by
+ apply WP.of_runBlock
+ simp only [advance, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ State.load64, ea_at, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ RegUpd.mem_setReg, RegUpd.mem_arithFlags, RegUpd.rd_setReg, RegUpd.rd_arithFlags,
+ RegUpd.wr_setReg, RegUpd.wr_arithFlags, RegUpd.cf_arithFlags, hr,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ reduceCtorEq, ite_true, ite_false, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem context_ready {p : Params} {pass lane slice index old : Nat} {s : State}
+ (h : FillContext.Ready p pass lane slice index old s) : SegmentSetup.Ready p pass lane slice s :=
+ ⟨h.parameters, h.layout, h.cache.layout, h.cache.reads, h.cache.write, ⟨old, h.cache.words⟩,
+ h.matrixWork, h.position.laneLength, h.position.segmentLength, h.lanesWord⟩
+
+theorem finished_ready {p : Params} {pass lane slice : Nat} {s t : State} {state : FillState}
+ (parameters : FillContext.Parameters p pass lane slice) (h : FillSegment.Finished s t p pass lane slice state) :
+ SegmentSetup.Ready p pass lane slice t := by
+ obtain ⟨old, context⟩ := FillContext.finished_context parameters h
+ exact context_ready context
+
+theorem change_lane_ready {p : Params} {pass lane slice newLane : Nat} {s t : State}
+ (h : SegmentSetup.Ready p pass lane slice s) (k : Divide.Keeps [.rbx] s t)
+ (value : t.gpr .rbx = BitVec.ofNat 64 newLane) (active : newLane < p.lanes) :
+ SegmentSetup.Ready p pass newLane slice t := by
+ have bp := k.regs .rbp (by decide)
+ have sp := k.regs .rsp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [k.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [k.mem, bp]
+ refine ⟨{ h.parameters with laneBound := active }, h.layout.of_preserved bp sp base work k.rd k.wr,
+ ?_, ?_, ?_, ?_, ?_, (k.regs .r12 (by decide)).trans h.laneLength,
+ (k.regs .r13 (by decide)).trans h.segmentLength, ?_⟩
+ · constructor
+ · rw [k.rd, k.wr, bp]; exact h.addressLayout.frameRead
+ · rw [k.wr, work]; exact h.addressLayout.workWrite
+ · rw [bp, work]; exact h.addressLayout.frameWork
+ · rw [bp, sp]; exact h.addressLayout.frameStack
+ · rw [sp, work]; exact h.addressLayout.stackWork
+ · rw [k.rd, k.wr, bp]; exact h.reads
+ · rw [k.wr, bp]; exact h.write
+ · obtain ⟨old, words⟩ := h.words
+ refine ⟨old, ?_, value, (k.regs .r14 (by decide)).trans words.sliceWord, ?_, ?_, ?_, ?_⟩
+ all_goals rw [k.mem, bp]
+ · exact words.passWord
+ · exact words.blocksWord
+ · exact words.passesWord
+ · exact words.variantWord
+ · exact words.counterWord
+ · rw [base, work]; exact h.matrixWork
+ · rw [k.mem, bp]; exact h.lanesWord
+
+end VG.Proof.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanes.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanes.lean
new file mode 100644
index 000000000..a396f0f1b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanes.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanesBody
+import VerifiedGarbage.Proof.Argon2.Lanes
+
+/-! Termination and correctness of all remaining lanes in one slice. -/
+
+namespace VG.Proof.Argon2.X86_64.FillLanes
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Finished (s t : State) (p : Params) (pass slice : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ laneWord : t.gpr .rbx = BitVec.ofNat 64 p.lanes
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r15 → t.gpr r = s.gpr r
+ header : ∃ lane a, lane + 1 = p.lanes ∧ SegmentSetup.Ready p pass lane slice a ∧ Divide.Keeps [.rbx] a t
+
+theorem Done.finished {s t : State} {p : Params} {pass lane slice : Nat} {state : FillState}
+ (h : Done s t p pass lane slice state) (last : lane + 1 = p.lanes) :
+ Finished s t p pass slice (Proof.Argon2.segment p pass lane slice 0 p.segmentLen state) := by
+ obtain ⟨a, ready, keeps⟩ := h.header
+ exact ⟨h.represented, h.matrix, h.work, last ▸ h.laneWord, h.rd, h.wr, h.frame, h.mxcsr,
+ h.regs, lane, a, last, ready, keeps⟩
+
+theorem Finished.prepend {s a t : State} {p : Params} {pass lane slice : Nat} {state finalState : FillState}
+ (first : Done s a p pass lane slice state) (rest : Finished a t p pass slice finalState) :
+ Finished s t p pass slice finalState := by
+ refine ⟨rest.represented, rest.matrix.trans first.matrix, rest.work.trans first.work, rest.laneWord,
+ rest.rd.trans first.rd, rest.wr.trans first.wr, ?_, rest.mxcsr.trans first.mxcsr, ?_, rest.header⟩
+ · have frame := rest.frame
+ rw [FillBlock.writes, first.matrix, first.work,
+ first.regs .rsp (by simp [calleeSaved]) (by decide) (by decide),
+ first.regs .rbp (by simp [calleeSaved]) (by decide) (by decide)] at frame
+ exact first.frame.trans frame
+ · intro r hr bx ix; exact (rest.regs r hr bx ix).trans (first.regs r hr bx ix)
+
+theorem loop_ok (count : Nat) (s : State) (p : Params) (pass lane slice : Nat)
+ (h : SegmentSetup.Ready p pass lane slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (positive : 0 < count) (endLane : lane + count = p.lanes) :
+ WP isa Impl.Argon2.X86_64.FillLanes.loop s
+ (Finished s · p pass slice (Proof.Argon2.lanes p pass slice lane count state)) := by
+ induction count generalizing s lane state with
+ | zero => omega
+ | succ n ih =>
+ obtain ⟨trace, a, run, done⟩ := body_ok s p pass lane slice h state represented
+ rw [Proof.Argon2.lanes_succ]
+ cases n with
+ | zero =>
+ have last : lane + 1 = p.lanes := endLane
+ refine ⟨_, a, .loopExit run ?_, done.finished last⟩
+ simp only [eval, done.cf, last, Nat.lt_irrefl, decide_false]
+ | succ n =>
+ have active : lane + 1 < p.lanes := by omega
+ obtain ⟨restTrace, t, restRun, finished⟩ := ih a (lane + 1) (done.next active)
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen state) done.represented (by omega) (by omega)
+ refine ⟨_, t, .loopNext run ?_ restRun, finished.prepend done⟩
+ simp only [eval, done.cf, active, decide_true]
+
+end VG.Proof.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBody.lean
new file mode 100644
index 000000000..94f71d2fd
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBody.lean
@@ -0,0 +1,54 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLaneAdvance
+
+/-! One lane iteration fills its segment and advances the public lane. -/
+
+namespace VG.Proof.Argon2.X86_64.FillLanes
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Done (s t : State) (p : Params) (pass lane slice : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen state).memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ laneWord : t.gpr .rbx = BitVec.ofNat 64 (lane + 1)
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r15 → t.gpr r = s.gpr r
+ header : ∃ a, SegmentSetup.Ready p pass lane slice a ∧ Divide.Keeps [.rbx] a t
+ cf : t.cf = decide (lane + 1 < p.lanes)
+ next : lane + 1 < p.lanes → SegmentSetup.Ready p pass (lane + 1) slice t
+
+theorem body_ok (s : State) (p : Params) (pass lane slice : Nat)
+ (h : SegmentSetup.Ready p pass lane slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillLanes.body s (Done s · p pass lane slice state) := by
+ unfold Impl.Argon2.X86_64.FillLanes.body
+ refine WP.seq ((SegmentSetup.code_ok s p pass lane slice h state represented).mono ?_)
+ intro a filled
+ have ready := finished_ready h.parameters filled
+ refine (advance_ok a (ready.layout.frameRead 184 (by simp))).mono ?_
+ rintro t ⟨value, flag, keeps⟩
+ obtain ⟨old, words⟩ := ready.words
+ have bp := keeps.regs .rbp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix a := by unfold FillKernel.matrix; rw [keeps.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work a := by unfold AddressCalls.work; rw [keeps.mem, bp]
+ have added : a.gpr .rbx + 1 = BitVec.ofNat 64 (lane + 1) := by
+ rw [words.laneWord, BitVec.ofNat_add]; rfl
+ have nextWord := value.trans added
+ have lanesBound : p.lanes < 2 ^ 64 := Nat.lt_trans h.parameters.lanesBound (by decide)
+ have laneBound := h.parameters.laneBound
+ refine ⟨?_, base.trans filled.matrix, work.trans filled.work, nextWord, keeps.rd.trans filled.rd,
+ keeps.wr.trans filled.wr, ?_, keeps.mxcsr.trans filled.mxcsr, ?_, ⟨a, ready, keeps⟩, ?_, ?_⟩
+ · rw [keeps.mem, base]; exact filled.represented
+ · rw [keeps.mem]; exact filled.frame
+ · intro r hr bx ix
+ have outside : r ∉ [Reg.rbx] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact bx
+ exact (keeps.regs r outside).trans (filled.regs r hr ix)
+ · rw [flag, added, ready.lanesWord, ReferenceMap.word_nat (lane + 1) (by omega),
+ ReferenceMap.word_nat p.lanes lanesBound]
+ · intro active; exact change_lane_ready ready keeps nextWord active
+
+end VG.Proof.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBodyCT.lean
new file mode 100644
index 000000000..615877731
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesBodyCT.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanesBody
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupCT
+
+/-! Lane iteration preserves public allocations and loops on the public lane count. -/
+
+namespace VG.Proof.Argon2.X86_64.FillLanes
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillLanes
+
+theorem advance_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp) (.block advance) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+structure NextRelated (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ ready : SegmentSetup.RelatedReady p pass lane slice s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+
+theorem body_rel (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (SegmentSetup.Related p pass lane slice leftState rightState) body
+ (fun s t => s.cf = t.cf ∧ (lane + 1 < p.lanes → NextRelated p pass (lane + 1) slice
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen leftState)
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen rightState) s t)) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq segmentA advanceA =>
+ cases eb with
+ | seq segmentB advanceB =>
+ obtain ⟨segmentTrace, _⟩ := SegmentSetup.code_rel p pass lane slice leftState rightState
+ _ _ _ _ _ _ hp segmentA segmentB
+ obtain ⟨_, sa, runA, filledA⟩ := SegmentSetup.code_ok s p pass lane slice hp.ready.left leftState hp.leftMatrix
+ obtain ⟨_, sb, runB, filledB⟩ := SegmentSetup.code_ok t p pass lane slice hp.ready.right rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det segmentA runA
+ obtain ⟨_, rfl⟩ := Exec.det segmentB runB
+ have bases := (filledA.regs .rbp (by simp [calleeSaved]) (by decide)).trans
+ (hp.ready.bases.trans (filledB.regs .rbp (by simp [calleeSaved]) (by decide)).symm)
+ obtain ⟨advancedTrace, _⟩ := advance_rel _ _ _ _ _ _ bases advanceA advanceB
+ obtain ⟨_, a', runA, doneA⟩ := body_ok s p pass lane slice hp.ready.left leftState hp.leftMatrix
+ obtain ⟨_, b', runB, doneB⟩ := body_ok t p pass lane slice hp.ready.right rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det (.seq segmentA advanceA) runA
+ obtain ⟨_, rfl⟩ := Exec.det (.seq segmentB advanceB) runB
+ refine ⟨by rw [segmentTrace, advancedTrace], doneA.cf.trans doneB.cf.symm, ?_⟩
+ intro active
+ refine ⟨⟨doneA.next active, doneB.next active, ?_, ?_,
+ doneA.matrix.trans (hp.ready.matrices.trans doneB.matrix.symm),
+ doneA.work.trans (hp.ready.work.trans doneB.work.symm)⟩, doneA.represented, doneB.represented⟩
+ · exact (doneA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide)).trans
+ (hp.ready.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide)).symm)
+ · exact (doneA.regs .rsp (by simp [calleeSaved]) (by decide) (by decide)).trans
+ (hp.ready.stacks.trans (doneB.regs .rsp (by simp [calleeSaved]) (by decide) (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesCT.lean
new file mode 100644
index 000000000..0b33492ef
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillLanesCT.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanes
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanesBodyCT
+import VerifiedGarbage.Proof.Argon2.LanesIndices
+
+/-! The lane loop exposes only the slice's specified reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillLanes
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass lane slice count : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ ready : SegmentSetup.RelatedReady p pass lane slice s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.lanes p pass slice lane count leftState).indices =
+ (Proof.Argon2.lanes p pass slice lane count rightState).indices
+
+theorem loop_rel (p : Params) (pass lane slice count : Nat) (leftState rightState : FillState)
+ (positive : 0 < count) (endLane : lane + count = p.lanes) :
+ RelCT isa (Related p pass lane slice count leftState rightState) Impl.Argon2.X86_64.FillLanes.loop (fun _ _ => True) := by
+ let I := fun n s t => ∃ (lane : Nat) (leftState rightState : FillState),
+ lane + n = p.lanes ∧ 0 < n ∧ Related p pass lane slice n leftState rightState s t
+ have steps : ∀ n, RelCT isa (I n) Impl.Argon2.X86_64.FillLanes.body fun s t =>
+ isa.eval .b s = isa.eval .b t ∧ (isa.eval .b s = some false → True) ∧
+ (isa.eval .b s = some true → ∃ m < n, I m s t) := by
+ intro n s t ts tt a b hp ea eb
+ obtain ⟨j, ls, rs, endLane, positive, hp⟩ := hp
+ cases n with
+ | zero => omega
+ | succ n =>
+ have segmentRelated : SegmentSetup.Related p pass j slice ls rs s t :=
+ ⟨hp.ready, hp.leftMatrix, hp.rightMatrix, Proof.Argon2.lanes_first_segment p pass slice j n ls rs
+ hp.ready.left.parameters.segment_bound.1 hp.indices⟩
+ obtain ⟨trace, flags, next⟩ := body_rel p pass j slice ls rs _ _ _ _ _ _ segmentRelated ea eb
+ obtain ⟨_, a', runA, done⟩ := body_ok s p pass j slice hp.ready.left ls hp.leftMatrix
+ obtain ⟨_, rfl⟩ := Exec.det ea runA
+ refine ⟨trace, ?_, fun _ => trivial, ?_⟩
+ · simp only [eval, flags]
+ · intro taken
+ have active : j + 1 < p.lanes := by
+ simp only [eval, done.cf, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ obtain ⟨ready, matrixA, matrixB⟩ := next active
+ have indices := hp.indices
+ rw [Proof.Argon2.lanes_succ, Proof.Argon2.lanes_succ] at indices
+ exact ⟨n, by omega, j + 1, Proof.Argon2.segment p pass j slice 0 p.segmentLen ls,
+ Proof.Argon2.segment p pass j slice 0 p.segmentLen rs, by omega, by omega, ready, matrixA, matrixB, indices⟩
+ exact (RelCT.loop I steps count).mono
+ (fun _ _ h => ⟨lane, leftState, rightState, endLane, positive, h⟩) (fun _ _ h => h)
+
+end VG.Proof.Argon2.X86_64.FillLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassCounter.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassCounter.lean
new file mode 100644
index 000000000..3a9765a34
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassCounter.lean
@@ -0,0 +1,42 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillIterations
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIteration
+
+/-! Increment, save and compare the public pass counter. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillIterations
+
+theorem increment_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8) :
+ WP isa (.block increment) s fun t =>
+ t.gpr .rax = s.mem.readW (off (s.gpr .rbp) 0) 64 + 1 ∧ Divide.Keeps [.rax] s t := by
+ apply WP.of_runBlock
+ simp only [increment, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, hr, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ ite_true, Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem saveCheck_ok (s : State) (hw : InRegions s.wr (off (s.gpr .rbp) 0) 8)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 72) 8) :
+ WP isa (.block saveCheck) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rbp) 0) (s.gpr .rax) ∧
+ t.gpr = s.gpr ∧ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr ∧
+ t.cf = decide ((s.gpr .rax).toNat < (s.mem.readW (off (s.gpr .rbp) 72) 64).toNat) := by
+ apply WP.of_runBlock
+ have sep : Mem.Sep (off (s.gpr .rbp) 72) 8 (off (s.gpr .rbp) 0) 8 :=
+ Offset.sep _ (by decide) (by decide) (by decide)
+ simp only [saveCheck, runBlock_cons, runStep_some, runBlock_nil, exec, State.store64,
+ State.load64, ea_at, hw, hr, readSrc, execAlu, ite_true,
+ Mem.readW_writeW_sep (w := 64) (w' := 64) sep (by decide), RegUpd.cf_arithFlags,
+ RegUpd.mem_arithFlags, RegUpd.gpr_arithFlags, RegUpd.rd_arithFlags, RegUpd.wr_arithFlags,
+ Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, trivial, trivial, ?_, trivial⟩
+ rfl
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassSave.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassSave.lean
new file mode 100644
index 000000000..2c52b6633
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPassSave.lean
@@ -0,0 +1,79 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPassCounter
+
+/-! A saved pass counter changes only its eight-byte header word. -/
+
+namespace VG.Proof.Argon2.X86_64.FillIterations
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillIterations
+
+structure Saved (s t : State) : Prop where
+ mem : t.mem = s.mem.writeW (off (s.gpr .rbp) 0) (s.mem.readW (off (s.gpr .rbp) 0) 64 + 1)
+ regs : ∀ r, r ≠ .rax → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+ cf : t.cf = decide ((s.mem.readW (off (s.gpr .rbp) 0) 64 + 1).toNat <
+ (s.mem.readW (off (s.gpr .rbp) 72) 64).toNat)
+ frame : Frame [⟨off (s.gpr .rbp) 0, 8⟩] s.mem t.mem
+
+theorem advance_ok (s : State) (read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8)
+ (write : InRegions s.wr (off (s.gpr .rbp) 0) 8)
+ (passesRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 72) 8) : WP isa advance s (Saved s) := by
+ unfold advance
+ refine WP.seq ((increment_ok s read).mono ?_)
+ rintro a ⟨value, keeps⟩
+ have bp := keeps.regs .rbp (by decide)
+ have readA : InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) 72) 8 := by
+ rw [keeps.rd, keeps.wr, bp]; exact passesRead
+ have writeA : InRegions a.wr (off (a.gpr .rbp) 0) 8 := by rw [keeps.wr, bp]; exact write
+ refine (saveCheck_ok a writeA readA).mono ?_
+ rintro t ⟨mem, regs, rd, wr, mx, cf⟩
+ have finalMem : t.mem = s.mem.writeW (off (s.gpr .rbp) 0) (s.mem.readW (off (s.gpr .rbp) 0) 64 + 1) := by
+ rw [mem, keeps.mem, bp, value]
+ refine ⟨finalMem, ?_, rd.trans keeps.rd, wr.trans keeps.wr, mx.trans keeps.mxcsr, ?_, ?_⟩
+ · intro r ne
+ have outside : r ∉ [Reg.rax] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact ne
+ exact (congrFun regs r).trans (keeps.regs r outside)
+ · rw [cf, value, keeps.mem, bp]
+ · rw [finalMem]
+ exact (Frame.refl _ _).writeW (r := ⟨off (s.gpr .rbp) 0, 8⟩) (by simp) _ (Region.contains_self _ _)
+
+theorem Saved.read {s t : State} (h : Saved s t) (d : Nat) (separate : 8 ≤ d) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [h.regs .rbp (by decide), h.mem]
+ exact Mem.readW_writeW_sep (Offset.sep _ (d := d) (n := 8) (e := 0) (k := 8)
+ (by omega) (by omega) (by decide)) (by decide)
+
+theorem Saved.words {s t : State} {p : Params} {pass lane slice old : Nat}
+ (h : Saved s t) (words : AddressHeader.Words p pass lane slice old s) :
+ AddressHeader.Words p (pass + 1) lane slice old t := by
+ refine ⟨?_, (h.regs .rbx (by decide)).trans words.laneWord,
+ (h.regs .r14 (by decide)).trans words.sliceWord,
+ (h.read 240 (by decide) (by decide)).trans words.blocksWord,
+ (h.read 72 (by decide) (by decide)).trans words.passesWord,
+ (h.read 112 (by decide) (by decide)).trans words.variantWord,
+ (h.read 8 (by decide) (by decide)).trans words.counterWord⟩
+ rw [h.regs .rbp (by decide), h.mem, Mem.readW_writeW_self64, words.passWord, BitVec.ofNat_add]
+ rfl
+
+theorem Saved.header {s t : State} {p : Params} {pass lane slice : Nat}
+ (h : Saved s t) (header : FillHeader.Ready p pass lane slice s) : FillHeader.Ready p (pass + 1) lane slice t := by
+ have bp := h.regs .rbp (by decide)
+ have sp := h.regs .rsp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix s := h.read 232 (by decide) (by decide)
+ have work : AddressCalls.work t = AddressCalls.work s := h.read 248 (by decide) (by decide)
+ refine ⟨header.layout.of_preserved bp sp base work h.rd h.wr, ?_, ?_, ?_, ?_, ?_,
+ (h.regs .r12 (by decide)).trans header.laneLength, (h.regs .r13 (by decide)).trans header.segmentLength, ?_⟩
+ · constructor
+ · rw [h.rd, h.wr, bp]; exact header.addressLayout.frameRead
+ · rw [h.wr, work]; exact header.addressLayout.workWrite
+ · rw [bp, work]; exact header.addressLayout.frameWork
+ · rw [bp, sp]; exact header.addressLayout.frameStack
+ · rw [sp, work]; exact header.addressLayout.stackWork
+ · rw [h.rd, h.wr, bp]; exact header.reads
+ · rw [h.wr, bp]; exact header.write
+ · obtain ⟨old, words⟩ := header.words; exact ⟨old, h.words words⟩
+ · rw [base, work]; exact header.matrixWork
+ · exact (h.read 184 (by decide) (by decide)).trans header.lanesWord
+
+end VG.Proof.Argon2.X86_64.FillIterations
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointers.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointers.lean
new file mode 100644
index 000000000..346b12d82
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointers.lean
@@ -0,0 +1,99 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointersArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.FillColumn
+import VerifiedGarbage.Proof.Argon2.X86_64.BlockAddress
+
+/-! Compose the matrix addresses while retaining the enclosing loop position. -/
+
+namespace VG.Proof.Argon2.X86_64.FillPointers
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillPointers
+
+def address (base lane column q : Addr) : Addr := (lane * q + column) * 1024 + base
+
+def column (s : State) : Addr := s.gpr .r14 * s.gpr .r13 + s.gpr .r15
+
+def predecessor (s : State) : Addr :=
+ (if column s = 0 then s.gpr .r12 else column s) - 1
+
+def changed : List Reg := [.rax, .rdx, .rcx, .rdi, .rsi, .r10, .r11]
+
+theorem current_ok (s : State) : WP isa current s fun t =>
+ t.gpr .rax = address (s.gpr .r8) (s.gpr .rbx) (s.gpr .rcx) (s.gpr .r12) ∧
+ Divide.Keeps [.rax, .rdx] s t := by
+ unfold current
+ refine WP.seq ((currentArgs_ok s).mono ?_)
+ rintro a ⟨lane, ka⟩
+ refine (BlockAddress.code_ok a).mono ?_
+ rintro t ⟨pointer, kt⟩
+ refine ⟨?_, (ka.mono (by simp)).trans kt⟩
+ rw [pointer, lane, ka.regs .r12 (by decide), ka.regs .rcx (by decide), ka.regs .r8 (by decide), address]
+
+theorem previous_ok (s : State) : WP isa previous s fun t =>
+ t.gpr .r10 = s.gpr .rax ∧
+ t.gpr .rax = address (s.gpr .r8) (s.gpr .rbx) (s.gpr .rdi) (s.gpr .r12) ∧
+ Divide.Keeps [.rax, .rdx, .rcx, .r10] s t := by
+ unfold previous
+ refine WP.seq ((previousArgs_ok s).mono ?_)
+ rintro a ⟨saved, col, lane, ka⟩
+ refine (BlockAddress.code_ok a).mono ?_
+ rintro t ⟨pointer, kt⟩
+ refine ⟨(kt.regs .r10 (by decide)).trans saved, ?_,
+ (ka.mono (by simp)).trans (kt.mono (by simp))⟩
+ rw [pointer, lane, col, ka.regs .r12 (by decide), ka.regs .r8 (by decide), address]
+
+theorem reference_ok (s : State) : WP isa reference s fun t =>
+ t.gpr .r11 = s.gpr .rax ∧
+ t.gpr .rax = address (s.gpr .r8) (s.gpr .r9) (s.gpr .rsi) (s.gpr .r12) ∧
+ Divide.Keeps [.rax, .rdx, .rcx, .r11] s t := by
+ unfold reference
+ refine WP.seq ((referenceArgs_ok s).mono ?_)
+ rintro a ⟨saved, col, lane, ka⟩
+ refine (BlockAddress.code_ok a).mono ?_
+ rintro t ⟨pointer, kt⟩
+ refine ⟨(kt.regs .r11 (by decide)).trans saved, ?_,
+ (ka.mono (by simp)).trans (kt.mono (by simp))⟩
+ rw [pointer, lane, col, ka.regs .r12 (by decide), ka.regs .r8 (by decide), address]
+
+theorem code_ok (s : State) : WP isa code s fun t =>
+ t.gpr .r10 = address (s.gpr .r8) (s.gpr .rbx) (column s) (s.gpr .r12) ∧
+ t.gpr .rdi = address (s.gpr .r8) (s.gpr .rbx) (predecessor s) (s.gpr .r12) ∧
+ t.gpr .rsi = address (s.gpr .r8) (s.gpr .r9) (s.gpr .rdi) (s.gpr .r12) ∧
+ Divide.Keeps changed s t := by
+ unfold code
+ refine WP.seq ((saveReference_ok s).mono ?_)
+ rintro a ⟨refColumn, ka⟩
+ refine WP.seq ((FillColumn.code_ok a).mono ?_)
+ rintro b ⟨curColumn, prevColumn, kb⟩
+ refine WP.seq ((current_ok b).mono ?_)
+ rintro c ⟨curPointer, kc⟩
+ refine WP.seq ((previous_ok c).mono ?_)
+ rintro d ⟨savedCurrent, prevPointer, kd⟩
+ refine WP.seq ((reference_ok d).mono ?_)
+ rintro e ⟨savedPrevious, refPointer, ke⟩
+ refine (finishArgs_ok e).mono ?_
+ rintro t ⟨referenceResult, previousResult, kt⟩
+ have coords : column a = column s := by
+ unfold column
+ rw [ka.regs .r14 (by decide), ka.regs .r13 (by decide), ka.regs .r15 (by decide)]
+ refine ⟨?_, ?_, ?_, ?_⟩
+ · rw [kt.regs .r10 (by decide), ke.regs .r10 (by decide), savedCurrent, curPointer,
+ kb.regs .r8 (by decide), ka.regs .r8 (by decide), kb.regs .rbx (by decide),
+ ka.regs .rbx (by decide), kb.regs .r12 (by decide), ka.regs .r12 (by decide), curColumn]
+ exact congrArg (fun col => address (s.gpr .r8) (s.gpr .rbx) col (s.gpr .r12)) coords
+ · rw [previousResult, savedPrevious, prevPointer, kc.regs .r8 (by decide),
+ kc.regs .rbx (by decide), kc.regs .rdi (by decide), kc.regs .r12 (by decide),
+ kb.regs .r8 (by decide), ka.regs .r8 (by decide), kb.regs .rbx (by decide),
+ ka.regs .rbx (by decide), kb.regs .r12 (by decide), ka.regs .r12 (by decide), prevColumn]
+ change address _ _ ((if column a = 0 then a.gpr .r12 else column a) - 1) _ = _
+ rw [coords, ka.regs .r12 (by decide), predecessor]
+ · rw [referenceResult, refPointer, kd.regs .r8 (by decide), kd.regs .r9 (by decide),
+ kd.regs .rsi (by decide), kd.regs .r12 (by decide), kc.regs .r8 (by decide),
+ kc.regs .r9 (by decide), kc.regs .rsi (by decide), kc.regs .r12 (by decide),
+ kb.regs .r8 (by decide), kb.regs .r9 (by decide), kb.regs .rsi (by decide),
+ kb.regs .r12 (by decide), ka.regs .r8 (by decide), ka.regs .r9 (by decide),
+ ka.regs .r12 (by decide), refColumn]
+ · exact (((((ka.mono (by simp [changed])).trans (kb.mono (by simp [changed]))).trans
+ (kc.mono (by simp [changed]))).trans (kd.mono (by simp [changed]))).trans
+ (ke.mono (by simp [changed]))).trans (kt.mono (by simp [changed]))
+
+end VG.Proof.Argon2.X86_64.FillPointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersArgs.lean
new file mode 100644
index 000000000..f55ec4160
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersArgs.lean
@@ -0,0 +1,76 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillPointers
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! Short register-setup steps for the filling pointers. -/
+
+namespace VG.Proof.Argon2.X86_64.FillPointers
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillPointers
+
+theorem saveReference_ok (s : State) : WP isa (.block saveReference) s fun t =>
+ t.gpr .rsi = s.gpr .rdi ∧ Divide.Keeps [.rsi] s t := by
+ apply WP.of_runBlock
+ simp only [saveReference, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ exact ite_eq_right hr
+ all_goals rfl
+
+theorem currentArgs_ok (s : State) : WP isa (.block currentArgs) s fun t =>
+ t.gpr .rax = s.gpr .rbx ∧ Divide.Keeps [.rax] s t := by
+ apply WP.of_runBlock
+ simp only [currentArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg, ite_true]
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ exact ite_eq_right hr
+ all_goals rfl
+
+theorem previousArgs_ok (s : State) : WP isa (.block previousArgs) s fun t =>
+ t.gpr .r10 = s.gpr .rax ∧ t.gpr .rcx = s.gpr .rdi ∧ t.gpr .rax = s.gpr .rbx ∧
+ Divide.Keeps [.r10, .rcx, .rax] s t := by
+ apply WP.of_runBlock
+ simp only [previousArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg,
+ reduceCtorEq, ite_true, ite_false]
+ refine ⟨trivial, trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem referenceArgs_ok (s : State) : WP isa (.block referenceArgs) s fun t =>
+ t.gpr .r11 = s.gpr .rax ∧ t.gpr .rcx = s.gpr .rsi ∧ t.gpr .rax = s.gpr .r9 ∧
+ Divide.Keeps [.r11, .rcx, .rax] s t := by
+ apply WP.of_runBlock
+ simp only [referenceArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg,
+ reduceCtorEq, ite_true, ite_false]
+ refine ⟨trivial, trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem finishArgs_ok (s : State) : WP isa (.block finishArgs) s fun t =>
+ t.gpr .rsi = s.gpr .rax ∧ t.gpr .rdi = s.gpr .r11 ∧
+ Divide.Keeps [.rsi, .rdi] s t := by
+ apply WP.of_runBlock
+ simp only [finishArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg,
+ reduceCtorEq, ite_true, ite_false]
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.FillPointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersCT.lean
new file mode 100644
index 000000000..b531c5cc2
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersCT.lean
@@ -0,0 +1,17 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointersLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! The pointer setup only branches on the public current column.
+Reference coordinates may differ without changing its execution trace. -/
+
+namespace VG.Proof.Argon2.X86_64.FillPointers
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillPointers
+
+theorem code_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.r8, .rbx, .r12, .r13, .r14, .r15], s.gpr r = t.gpr r) code
+ (fun s t => ∀ r ∈ [Reg.r10], s.gpr r = t.gpr r) :=
+ RelCT.taintRegs (τ := Taint.ofRegs [.r8, .rbx, .r12, .r13, .r14, .r15])
+ (fun _ _ h => Taint.agree_ofRegs h) [Reg.r10] (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.FillPointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersLit.lean
new file mode 100644
index 000000000..6b85e3f1f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.FillPointers
+
+/-! Checked literal of the complete filling pointer setup. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.FillPointers.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersNat.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersNat.lean
new file mode 100644
index 000000000..1e4643517
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillPointersNat.lean
@@ -0,0 +1,51 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillPointers
+import VerifiedGarbage.Proof.Argon2.X86_64.Memory
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapState
+import VerifiedGarbage.Proof.Argon2.FillPositions
+
+/-! Matrix pointers are the natural-number block offsets in the specification. -/
+
+namespace VG.Proof.Argon2.X86_64.FillPointers
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillPointers
+
+def cell (base : Addr) (p : Spec.Argon2.Params) (lane column : Nat) : Addr :=
+ off base ((lane * p.laneLen + column) * 1024)
+
+theorem address_nat (base : Addr) (lane column q : Nat) :
+ address base (BitVec.ofNat 64 lane) (BitVec.ofNat 64 column) (BitVec.ofNat 64 q) =
+ off base ((lane * q + column) * 1024) := by
+ unfold address off
+ change (BitVec.ofNat 64 lane * BitVec.ofNat 64 q + BitVec.ofNat 64 column) *
+ BitVec.ofNat 64 1024 + base = _
+ rw [← BitVec.ofNat_mul, ← BitVec.ofNat_add, ← BitVec.ofNat_mul, BitVec.add_comm]
+
+theorem code_nat_ok (s : State) (p : Spec.Argon2.Params) (pass lane slice index refLane refColumn : Nat)
+ (bounds : ReferenceMap.Bounds p pass lane slice index)
+ (position : ReferenceMap.Position p lane slice index s)
+ (rl : s.gpr .r9 = BitVec.ofNat 64 refLane)
+ (rc : s.gpr .rdi = BitVec.ofNat 64 refColumn) :
+ WP isa code s fun t =>
+ t.gpr .r10 = cell (s.gpr .r8) p lane (slice * p.segmentLen + index) ∧
+ t.gpr .rdi = cell (s.gpr .r8) p lane ((slice * p.segmentLen + index + p.laneLen - 1) % p.laneLen) ∧
+ t.gpr .rsi = cell (s.gpr .r8) p refLane refColumn ∧ Divide.Keeps changed s t := by
+ refine (code_ok s).mono ?_
+ rintro t ⟨current, previous, reference, keeps⟩
+ have col : column s = BitVec.ofNat 64 (slice * p.segmentLen + index) := by
+ unfold column
+ rw [position.slice, position.segmentLength, position.index, ← BitVec.ofNat_mul, ← BitVec.ofNat_add]
+ have prev : predecessor s = BitVec.ofNat 64
+ ((slice * p.segmentLen + index + p.laneLen - 1) % p.laneLen) := by
+ unfold predecessor
+ rw [col, position.laneLength]
+ have positive := Proof.Argon2.segmentLen_ge_two p bounds.lanesPositive bounds.memoryMinimum
+ have q := Proof.Argon2.laneLen_segments p bounds.lanesPositive
+ exact FillColumn.previous_word_nat _ _ (by omega)
+ (Nat.lt_trans bounds.laneLength_bound (by decide))
+ (Proof.Argon2.column_lt p bounds.lanesPositive bounds.sliceBound bounds.indexBound)
+ refine ⟨?_, ?_, ?_, keeps⟩
+ · rw [current, position.current, col, position.laneLength, address_nat]; rfl
+ · rw [previous, position.current, prev, position.laneLength, address_nat]; rfl
+ · rw [reference, rl, rc, position.laneLength, address_nat]; rfl
+
+end VG.Proof.Argon2.X86_64.FillPointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegment.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegment.lean
new file mode 100644
index 000000000..877df68e4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegment.lean
@@ -0,0 +1,70 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegmentBody
+import VerifiedGarbage.Proof.Argon2.Segment
+
+/-! Termination and correctness of the active suffix of one segment. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSegment
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Finished (s t : State) (p : Params) (pass lane slice : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ position : ReferenceMap.Position p lane slice p.segmentLen t
+ layout : FillKernel.Layout p t
+ cache : ∃ old, AddressCache.Invariant p pass lane slice old t
+ matrixWork : (⟨FillKernel.matrix t, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work t, 8192⟩
+ passWord : t.mem.readW (off (t.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass
+ lanesWord : t.mem.readW (off (t.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+ regs : ∀ r ∈ calleeSaved, r ≠ .r15 → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem Done.finished {s t : State} {p : Params} {pass lane slice index : Nat} {state : FillState}
+ (h : Done s t p pass lane slice index state) (last : index + 1 = p.segmentLen) :
+ Finished s t p pass lane slice (fillBlock p pass slice lane index state) :=
+ ⟨h.represented, h.matrix, h.work, last ▸ h.position, h.layout, h.cache,
+ h.matrixWork, h.passWord, h.lanesWord, h.regs, h.rd, h.wr, h.frame, h.mxcsr⟩
+
+theorem Finished.prepend {s a t : State} {p : Params} {pass lane slice index : Nat}
+ {state finalState : FillState} (first : Done s a p pass lane slice index state)
+ (rest : Finished a t p pass lane slice finalState) : Finished s t p pass lane slice finalState := by
+ refine ⟨rest.represented, rest.matrix.trans first.matrix, rest.work.trans first.work,
+ rest.position, rest.layout, rest.cache, rest.matrixWork, rest.passWord, rest.lanesWord,
+ ?_, rest.rd.trans first.rd, rest.wr.trans first.wr, ?_, rest.mxcsr.trans first.mxcsr⟩
+ · intro r hr ne; exact (rest.regs r hr ne).trans (first.regs r hr ne)
+ · have frame := rest.frame
+ rw [FillBlock.writes, first.matrix, first.work,
+ first.regs .rsp (by simp [calleeSaved]) (by decide),
+ first.regs .rbp (by simp [calleeSaved]) (by decide)] at frame
+ exact first.frame.trans frame
+
+theorem loop_ok (count : Nat) (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : RandomSource.Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (positive : 0 < count) (endIndex : index + count = p.segmentLen) :
+ WP isa Impl.Argon2.X86_64.FillSegment.loop s
+ (Finished s · p pass lane slice (Proof.Argon2.segment p pass lane slice index count state)) := by
+ induction count generalizing s index old state with
+ | zero => omega
+ | succ n ih =>
+ obtain ⟨trace, a, run, done⟩ := body_ok s p pass lane slice index old h state represented
+ rw [Proof.Argon2.segment_succ]
+ cases n with
+ | zero =>
+ have last : index + 1 = p.segmentLen := endIndex
+ refine ⟨_, a, .loopExit run ?_, ?_⟩
+ · simp only [eval, done.cf, last, Nat.lt_irrefl, decide_false]
+ · exact done.finished last
+ | succ n =>
+ have active : index + 1 < p.segmentLen := by omega
+ obtain ⟨nextCounter, nextReady⟩ := done.next active
+ obtain ⟨restTrace, t, restRun, finished⟩ := ih a (index + 1) nextCounter nextReady
+ (fillBlock p pass slice lane index state) done.represented (by omega) (by omega)
+ refine ⟨_, t, .loopNext run ?_ restRun, finished.prepend done⟩
+ simp only [eval, done.cf, active, decide_true]
+
+end VG.Proof.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBody.lean
new file mode 100644
index 000000000..9223d2f44
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBody.lean
@@ -0,0 +1,63 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIndex
+import VerifiedGarbage.Proof.Argon2.X86_64.FillAllocation
+
+/-! One segment iteration updates the specified cell and advances its public index. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSegment
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Done (s t : State) (p : Params) (pass lane slice index : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks
+ (fillBlock p pass slice lane index state).memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ position : ReferenceMap.Position p lane slice (index + 1) t
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .r15 → t.gpr r = s.gpr r
+ layout : FillKernel.Layout p t
+ cache : ∃ old, AddressCache.Invariant p pass lane slice old t
+ matrixWork : (⟨FillKernel.matrix t, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work t, 8192⟩
+ passWord : t.mem.readW (off (t.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass
+ lanesWord : t.mem.readW (off (t.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+ cf : t.cf = decide (index + 1 < p.segmentLen)
+ next : index + 1 < p.segmentLen → ∃ old, RandomSource.Ready p pass lane slice (index + 1) old t
+
+theorem body_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : RandomSource.Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillSegment.body s (Done s · p pass lane slice index state) := by
+ unfold Impl.Argon2.X86_64.FillSegment.body
+ refine WP.seq ((FillBlock.code_ok s p pass lane slice index old h state represented).mono ?_)
+ intro a filled
+ obtain ⟨counter, ready⟩ := filled.ready
+ refine (advance_nat_ok a p pass lane slice index ready.filling).mono ?_
+ rintro t ⟨value, cf, keeps⟩
+ have bp := keeps.regs .rbp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix a := by unfold FillKernel.matrix; rw [keeps.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work a := by unfold AddressCalls.work; rw [keeps.mem, bp]
+ refine ⟨?_, base.trans filled.matrix, work.trans filled.work, ?_, keeps.rd.trans filled.rd,
+ keeps.wr.trans filled.wr, ?_, keeps.mxcsr.trans filled.mxcsr, ?_, ?_, ?_, ?_, ?_, ?_, cf, ?_⟩
+ · rw [keeps.mem, base]; exact filled.represented
+ · exact ⟨(keeps.regs .rbx (by decide)).trans ready.filling.position.current,
+ (keeps.regs .r12 (by decide)).trans ready.filling.position.laneLength,
+ (keeps.regs .r13 (by decide)).trans ready.filling.position.segmentLength,
+ (keeps.regs .r14 (by decide)).trans ready.filling.position.slice, value⟩
+ · rw [keeps.mem]; exact filled.frame
+ · intro r hr ne
+ have outside : r ∉ [Reg.r15] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact ne
+ exact (keeps.regs r outside).trans (filled.regs r hr)
+ · exact ready.filling.layout.of_preserved bp (keeps.regs .rsp (by decide)) base work keeps.rd keeps.wr
+ · exact ⟨counter, ready.cache.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide)) keeps.mem keeps.rd keeps.wr⟩
+ · rw [base, work]; exact ready.matrixWork
+ · rw [keeps.mem, bp]; exact ready.filling.passWord
+ · rw [keeps.mem, bp]; exact ready.filling.lanesWord
+ · intro active; exact ⟨counter, next_ready ready keeps value active⟩
+
+end VG.Proof.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBodyCT.lean
new file mode 100644
index 000000000..61463d80e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentBodyCT.lean
@@ -0,0 +1,82 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegmentBody
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlockCT
+import VerifiedGarbage.Proof.Argon2.X86_64.FillBlockCounter
+
+/-! Public counters and coordinates remain related across a segment iteration. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSegment
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillSegment
+
+theorem advance_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.r13, .r15], s.gpr r = t.gpr r)
+ (.block advance) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.r13, .r15])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+structure NextRelated (p : Params) (pass lane slice index : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ ready : ∃ old, RandomSource.Related p pass lane slice index old s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+
+theorem body_rel (p : Params) (pass lane slice index old : Nat) (leftState rightState : FillState) :
+ RelCT isa (FillBlock.Related p pass lane slice index old leftState rightState) body
+ (fun s t => s.cf = t.cf ∧ (index + 1 < p.segmentLen →
+ NextRelated p pass lane slice (index + 1)
+ (fillBlock p pass slice lane index leftState) (fillBlock p pass slice lane index rightState) s t)) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq fillA advanceA =>
+ cases eb with
+ | seq fillB advanceB =>
+ obtain ⟨filledTrace, _⟩ := FillBlock.code_rel p pass lane slice index old leftState rightState
+ _ _ _ _ _ _ hp fillA fillB
+ obtain ⟨_, sa, runA, filledA⟩ := FillBlock.code_ok s p pass lane slice index old hp.source.left leftState hp.leftMatrix
+ obtain ⟨_, sb, runB, filledB⟩ := FillBlock.code_ok t p pass lane slice index old hp.source.right rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det fillA runA
+ obtain ⟨_, rfl⟩ := Exec.det fillB runB
+ obtain ⟨counterA, readyA⟩ := filledA.ready
+ obtain ⟨counterB, readyB⟩ := filledB.ready
+ obtain ⟨advancedTrace, _⟩ := advance_rel _ _ _ _ _ _ (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact readyA.filling.position.segmentLength.trans readyB.filling.position.segmentLength.symm
+ · exact readyA.filling.position.index.trans readyB.filling.position.index.symm) advanceA advanceB
+ obtain ⟨_, a', advanceRunA, valueA, flagA, keptA⟩ := advance_nat_ok _ p pass lane slice index readyA.filling
+ obtain ⟨_, b', advanceRunB, valueB, flagB, keptB⟩ := advance_nat_ok _ p pass lane slice index readyB.filling
+ obtain ⟨_, rfl⟩ := Exec.det advanceA advanceRunA
+ obtain ⟨_, rfl⟩ := Exec.det advanceB advanceRunB
+ refine ⟨by rw [filledTrace, advancedTrace], flagA.trans flagB.symm, ?_⟩
+ intro active
+ have nextA := next_ready readyA keptA valueA active
+ have nextB := next_ready readyB keptB valueB active
+ have counterWordA := FillBlock.counter_run hp.source.left leftState hp.leftMatrix fillA
+ have counterWordB := FillBlock.counter_run hp.source.right rightState hp.rightMatrix fillB
+ have wordEq : BitVec.ofNat 64 counterA = BitVec.ofNat 64 counterB :=
+ readyA.cache.words.counterWord.symm.trans
+ (counterWordA.trans (counterWordB.symm.trans readyB.cache.words.counterWord))
+ have counters := (ReferenceMap.word_eq counterA counterB readyA.cache.bound readyB.cache.bound).mp wordEq
+ subst counterB
+ refine ⟨⟨counterA, nextA, nextB, ?_, ?_, ?_, ?_⟩, ?_, ?_⟩
+ · rw [keptA.regs .rbp (by decide), keptB.regs .rbp (by decide),
+ filledA.regs .rbp (by simp [calleeSaved]), filledB.regs .rbp (by simp [calleeSaved])]
+ exact hp.source.bases
+ · rw [keptA.regs .rsp (by decide), keptB.regs .rsp (by decide),
+ filledA.regs .rsp (by simp [calleeSaved]), filledB.regs .rsp (by simp [calleeSaved])]
+ exact hp.source.stacks
+ · unfold FillKernel.matrix
+ rw [keptA.mem, keptB.mem, keptA.regs .rbp (by decide), keptB.regs .rbp (by decide)]
+ exact filledA.matrix.trans (hp.source.matrices.trans filledB.matrix.symm)
+ · unfold AddressCalls.work
+ rw [keptA.mem, keptB.mem, keptA.regs .rbp (by decide), keptB.regs .rbp (by decide)]
+ exact filledA.work.trans (hp.source.work.trans filledB.work.symm)
+ · unfold FillKernel.matrix
+ rw [keptA.mem, keptA.regs .rbp (by decide)]
+ exact filledA.represented
+ · unfold FillKernel.matrix
+ rw [keptB.mem, keptB.regs .rbp (by decide)]
+ exact filledB.represented
+
+end VG.Proof.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentCT.lean
new file mode 100644
index 000000000..c1e1e44f0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSegmentCT.lean
@@ -0,0 +1,54 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegment
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegmentBodyCT
+import VerifiedGarbage.Proof.Argon2.SegmentIndices
+
+/-! The segment loop exposes only the reviewed segment reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSegment
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass lane slice index count old : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ source : RandomSource.Related p pass lane slice index old s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.segment p pass lane slice index count leftState).indices =
+ (Proof.Argon2.segment p pass lane slice index count rightState).indices
+
+theorem loop_rel (p : Params) (pass lane slice index count old : Nat) (leftState rightState : FillState)
+ (positive : 0 < count) (endIndex : index + count = p.segmentLen) :
+ RelCT isa (Related p pass lane slice index count old leftState rightState)
+ Impl.Argon2.X86_64.FillSegment.loop (fun _ _ => True) := by
+ let I := fun n s t => ∃ (index old : Nat) (leftState rightState : FillState),
+ index + n = p.segmentLen ∧ 0 < n ∧ Related p pass lane slice index n old leftState rightState s t
+ have steps : ∀ n, RelCT isa (I n) Impl.Argon2.X86_64.FillSegment.body fun s t =>
+ isa.eval .b s = isa.eval .b t ∧ (isa.eval .b s = some false → True) ∧
+ (isa.eval .b s = some true → ∃ m < n, I m s t) := by
+ intro n s t ts tt a b hp ea eb
+ obtain ⟨j, counter, ls, rs, endIndex, positive, hp⟩ := hp
+ cases n with
+ | zero => omega
+ | succ n =>
+ have blockRelated : FillBlock.Related p pass lane slice j counter ls rs s t :=
+ ⟨hp.source, hp.leftMatrix, hp.rightMatrix,
+ Proof.Argon2.segment_first_reference p pass lane slice j n ls rs
+ hp.source.left.filling.bounds.active hp.indices⟩
+ obtain ⟨trace, flags, next⟩ := body_rel p pass lane slice j counter ls rs _ _ _ _ _ _ blockRelated ea eb
+ obtain ⟨_, a', runA, done⟩ := body_ok s p pass lane slice j counter hp.source.left ls hp.leftMatrix
+ obtain ⟨_, rfl⟩ := Exec.det ea runA
+ refine ⟨trace, ?_, fun _ => trivial, ?_⟩
+ · simp only [eval, flags]
+ · intro taken
+ have active : j + 1 < p.segmentLen := by
+ simp only [eval, done.cf, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ obtain ⟨⟨nextCounter, ready⟩, matrixA, matrixB⟩ := next active
+ have indices := hp.indices
+ rw [Proof.Argon2.segment_succ, Proof.Argon2.segment_succ] at indices
+ exact ⟨n, by omega, j + 1, nextCounter, fillBlock p pass slice lane j ls,
+ fillBlock p pass slice lane j rs, by omega, by omega, ready, matrixA, matrixB, indices⟩
+ exact (RelCT.loop I steps count).mono
+ (fun _ _ h => ⟨index, old, leftState, rightState, endIndex, positive, h⟩) (fun _ _ h => h)
+
+end VG.Proof.Argon2.X86_64.FillSegment
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetup.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetup.lean
new file mode 100644
index 000000000..5f98138f9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetup.lean
@@ -0,0 +1,75 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSetupEnvironment
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressSetup
+
+/-! Establish the complete pass-loop invariant from memory initialization's byte stride. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (s : State) : Prop where
+ environment : Environment p s
+ bound : 1024 * p.laneLen < 2 ^ 64
+ stride : s.gpr .r13 = BitVec.ofNat 64 (1024 * p.laneLen)
+
+structure Prepared (s t : State) (p : Params) : Prop where
+ ready : FillIterations.Ready p 0 t
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r12 → r ≠ .r13 → r ≠ .r14 → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨s.gpr .rbp, 8⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ words : ∀ d, 8 ≤ d → d + 8 ≤ 272 →
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64
+
+theorem code_ok (s : State) (p : Params) (h : Ready p s) :
+ WP isa Impl.Argon2.X86_64.FillSetup.code s (Prepared s · p) := by
+ unfold Impl.Argon2.X86_64.FillSetup.code
+ refine WP.seq ((dimensions_nat_ok s p h.bound h.stride).mono ?_)
+ rintro a ⟨laneLength, segmentLength, keeps⟩
+ have bp := keeps.regs .rbp (by decide)
+ have sp := keeps.regs .rsp (by decide)
+ have environment := h.environment.of_state bp sp keeps.mem keeps.rd keeps.wr
+ refine (reset_ok a environment.passWrite).mono ?_
+ intro t reset
+ have header := reset.header environment ((reset.regs .r12 (by decide)).trans laneLength)
+ ((reset.regs .r13 (by decide)).trans segmentLength)
+ have words (d : Nat) (lower : 8 ≤ d) (upper : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [reset.read d lower upper, keeps.mem, bp]
+ refine ⟨⟨⟨environment.parameters, 0, 0, header⟩, environment.passesBound, ?_⟩,
+ words 232 (by decide) (by decide), words 248 (by decide) (by decide), ?_,
+ reset.rd.trans keeps.rd, reset.wr.trans keeps.wr, ?_, reset.mxcsr.trans keeps.mxcsr, words⟩
+ · rw [reset.wr, reset.regs .rbp (by decide)]; exact environment.passWrite
+ · intro r hr bx q g sl
+ have ne : r ≠ .rax := by
+ 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 (reset.regs r (by simp only [List.mem_cons, List.not_mem_nil, or_false, not_or]; exact ⟨ne, bx, sl⟩)).trans
+ (keeps.regs r (by simp only [List.mem_cons, List.not_mem_nil, or_false, not_or]; exact ⟨q, g⟩))
+ · have frame := reset.frame
+ rw [bp, keeps.mem] at frame; exact frame
+
+theorem Prepared.represents {s t : State} {p : Params} (ready : Ready p s) (done : Prepared s t p)
+ (blocks : Array Block) (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks blocks) :
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks blocks := by
+ rw [done.matrix]
+ refine ⟨represented.size, ?_⟩
+ intro k hk
+ apply Eq.trans _ (represented.block k hk)
+ apply FillCompress.block_frame done.frame
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact (ready.environment.layout.matrixFrame.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)).sub_right
+ (Region.sub_prefix (by decide))
+
+theorem code_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ Impl.Argon2.X86_64.FillSetup.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupDimensions.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupDimensions.lean
new file mode 100644
index 000000000..eaf4d8447
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupDimensions.lean
@@ -0,0 +1,50 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+import VerifiedGarbage.Proof.Argon2.X86_64.Initialize
+import VerifiedGarbage.Proof.Argon2.Dimensions
+
+/-! Initialization's byte stride gives exact block and segment counts without division instructions. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem dimensions_ok (s : State) : WP isa (.block Impl.Argon2.X86_64.FillSetup.dimensions) s fun t =>
+ t.gpr .r12 = s.gpr .r13 >>> 10 ∧ t.gpr .r13 = s.gpr .r13 >>> 12 ∧ Divide.Keeps [.r12, .r13] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.FillSetup.dimensions, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ execShift, RegUpd.gpr_setReg, RegUpd.gpr_setFlags,
+ show 1 ≤ (10 : Nat) ∧ (10 : Nat) ≤ 63 from by decide,
+ show 1 ≤ (12 : Nat) ∧ (12 : Nat) ≤ 63 from by decide,
+ and_self, reduceCtorEq, ite_true, ite_false,
+ Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_setFlags, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem stride_shift (q shift : Nat) (bound : 1024 * q < 2 ^ 64) :
+ BitVec.ofNat 64 (1024 * q) >>> shift = BitVec.ofNat 64 ((1024 * q) / 2 ^ shift) := by
+ apply BitVec.eq_of_toNat_eq
+ rw [BitVec.toNat_ushiftRight, BitVec.toNat_ofNat, Nat.mod_eq_of_lt bound, Nat.shiftRight_eq_div_pow,
+ BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_of_le_of_lt (Nat.div_le_self _ _) bound)]
+
+theorem dimensions_nat_ok (s : State) (p : Params) (bound : 1024 * p.laneLen < 2 ^ 64)
+ (stride : s.gpr .r13 = BitVec.ofNat 64 (1024 * p.laneLen)) :
+ WP isa (.block Impl.Argon2.X86_64.FillSetup.dimensions) s fun t =>
+ t.gpr .r12 = BitVec.ofNat 64 p.laneLen ∧ t.gpr .r13 = BitVec.ofNat 64 p.segmentLen ∧
+ Divide.Keeps [.r12, .r13] s t := by
+ refine (dimensions_ok s).mono ?_
+ rintro t ⟨lane, segment, keeps⟩
+ refine ⟨?_, ?_, keeps⟩
+ · rw [lane, stride, stride_shift _ 10 bound]
+ simp only [show 2 ^ 10 = 1024 from rfl, Nat.mul_div_cancel_left _ (by decide : 0 < 1024)]
+ · rw [segment, stride, stride_shift _ 12 bound]
+ have div : 1024 * p.laneLen / 4096 = p.laneLen / 4 := by
+ rw [show (4096 : Nat) = 1024 * 4 from rfl, Nat.mul_div_mul_left _ _ (by decide : 0 < 1024)]
+ rw [show 2 ^ 12 = 4096 from rfl, div]
+ rfl
+
+end VG.Proof.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupEnvironment.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupEnvironment.lean
new file mode 100644
index 000000000..fc5c02822
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupEnvironment.lean
@@ -0,0 +1,71 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSetupReset
+import VerifiedGarbage.Proof.Argon2.X86_64.FillIterationsBody
+
+/-! Initialization hands filling the reviewed dimensions and stable public frame words. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Environment (p : Params) (s : State) : Prop where
+ parameters : FillContext.Parameters p 0 0 0
+ passesBound : p.passes < 2 ^ 32
+ layout : FillKernel.Layout p s
+ addressLayout : AddressCalls.Ready s
+ reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ counterWrite : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ passWrite : InRegions s.wr (off (s.gpr .rbp) 0) 8
+ matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩
+ blocksWord : s.mem.readW (off (s.gpr .rbp) 240) 64 = BitVec.ofNat 64 p.blocks
+ passesWord : s.mem.readW (off (s.gpr .rbp) 72) 64 = BitVec.ofNat 64 p.passes
+ variantWord : s.mem.readW (off (s.gpr .rbp) 112) 64 = BitVec.ofNat 64 p.variant.code
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+theorem Environment.of_state {p : Params} {s t : State} (h : Environment p s)
+ (bp : t.gpr .rbp = s.gpr .rbp) (sp : t.gpr .rsp = s.gpr .rsp)
+ (mem : t.mem = s.mem) (rd : t.rd = s.rd) (wr : t.wr = s.wr) : Environment p t := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [mem, bp]
+ refine ⟨h.parameters, h.passesBound, h.layout.of_preserved bp sp base work rd wr, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · constructor
+ · rw [rd, wr, bp]; exact h.addressLayout.frameRead
+ · rw [wr, work]; exact h.addressLayout.workWrite
+ · rw [bp, work]; exact h.addressLayout.frameWork
+ · rw [bp, sp]; exact h.addressLayout.frameStack
+ · rw [sp, work]; exact h.addressLayout.stackWork
+ · rw [rd, wr, bp]; exact h.reads
+ · rw [wr, bp]; exact h.counterWrite
+ · rw [wr, bp]; exact h.passWrite
+ · rw [base, work]; exact h.matrixWork
+ all_goals rw [mem, bp]
+ · exact h.blocksWord
+ · exact h.passesWord
+ · exact h.variantWord
+ · exact h.lanesWord
+
+theorem Reset.header {p : Params} {s t : State} (h : Environment p s) (reset : Reset s t)
+ (laneLength : t.gpr .r12 = BitVec.ofNat 64 p.laneLen)
+ (segmentLength : t.gpr .r13 = BitVec.ofNat 64 p.segmentLen) : FillHeader.Ready p 0 0 0 t := by
+ have bp := reset.regs .rbp (by decide)
+ have sp := reset.regs .rsp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix s := reset.read 232 (by decide) (by decide)
+ have work : AddressCalls.work t = AddressCalls.work s := reset.read 248 (by decide) (by decide)
+ refine ⟨h.layout.of_preserved bp sp base work reset.rd reset.wr, ?_, ?_, ?_, ?_, ?_, laneLength,
+ segmentLength, (reset.read 184 (by decide) (by decide)).trans h.lanesWord⟩
+ · constructor
+ · rw [reset.rd, reset.wr, bp]; exact h.addressLayout.frameRead
+ · rw [reset.wr, work]; exact h.addressLayout.workWrite
+ · rw [bp, work]; exact h.addressLayout.frameWork
+ · rw [bp, sp]; exact h.addressLayout.frameStack
+ · rw [sp, work]; exact h.addressLayout.stackWork
+ · rw [reset.rd, reset.wr, bp]; exact h.reads
+ · rw [reset.wr, bp]; exact h.counterWrite
+ · refine ⟨(s.mem.readW (off (s.gpr .rbp) 8) 64).toNat, reset.pass, reset.lane, reset.slice,
+ (reset.read 240 (by decide) (by decide)).trans h.blocksWord,
+ (reset.read 72 (by decide) (by decide)).trans h.passesWord,
+ (reset.read 112 (by decide) (by decide)).trans h.variantWord, ?_⟩
+ rw [reset.read 8 (by decide) (by decide)]
+ simp only [BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ · rw [base, work]; exact h.matrixWork
+
+end VG.Proof.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupFinish.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupFinish.lean
new file mode 100644
index 000000000..aa0eb418a
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupFinish.lean
@@ -0,0 +1,38 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.FillFinish
+
+/-! Filling setup retains the final-call layout and establishes the reduction dimensions. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Prepared.finish_ready {s t : State} {p : Params} (ready : Ready p s) (done : Prepared s t p)
+ (outputReady : FinalOutput.Ready p s) (positive : 0 < p.passes) : FillFinish.Ready p t := by
+ have bp := done.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)
+ have sp := done.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)
+ have base : ReductionState.matrix t = ReductionState.matrix s := done.matrix
+ have output : FinalOutput.output t = FinalOutput.output s := done.words 256 (by decide) (by decide)
+ have work : FinalOutput.work t = FinalOutput.work s := done.words 248 (by decide) (by decide)
+ obtain ⟨lane, slice, header⟩ := done.ready.filling.header
+ have params := ready.environment.parameters
+ refine ⟨done.ready, ⟨?_, ?_⟩, positive⟩
+ · refine ⟨⟨params.lanesPositive, params.segment_bound.1, ?_, header.layout.frameRead 232 (by simp),
+ header.layout.matrixWrite, header.layout.matrixFrame, header.laneLength⟩,
+ params.lanesBound, header.layout.frameRead 184 (by simp), header.lanesWord⟩
+ have blocks := Proof.Argon2.blocks_le_memory p
+ have memory := params.memoryBound
+ omega
+ · refine ⟨outputReady.positive, outputReady.bound, ?_,
+ (done.words 264 (by decide) (by decide)).trans outputReady.tagWord, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [done.rd, done.wr, bp]; exact outputReady.reads
+ · rw [base, done.rd, done.wr]; exact outputReady.input
+ · rw [output, done.wr]; exact outputReady.outputWrite
+ · rw [work, done.wr]; exact outputReady.workWrite
+ · rw [base, work]; exact outputReady.inputWork
+ · rw [output, work]; exact outputReady.outputWork
+ · rw [sp, base]; exact outputReady.stackInput
+ · rw [sp, output]; exact outputReady.stackOutput
+ · rw [sp, work]; exact outputReady.stackWork
+
+end VG.Proof.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupReset.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupReset.lean
new file mode 100644
index 000000000..1d1fd9870
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSetupReset.lean
@@ -0,0 +1,45 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSetupDimensions
+import VerifiedGarbage.Proof.Framework.Mem
+
+/-! Reset public loop coordinates and only the pass word in the enclosing frame. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Reset (s t : State) : Prop where
+ mem : t.mem = s.mem.writeW (off (s.gpr .rbp) 0) (0 : Addr)
+ lane : t.gpr .rbx = 0
+ slice : t.gpr .r14 = 0
+ regs : ∀ r, r ∉ [Reg.rax, .rbx, .r14] → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem reset_ok (s : State) (write : InRegions s.wr (off (s.gpr .rbp) 0) 8) :
+ WP isa (.block Impl.Argon2.X86_64.FillSetup.reset) s (Reset s) := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.FillSetup.reset, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ State.store64, ea_at, RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ write, reduceCtorEq, ite_true, ite_false, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨rfl, rfl, rfl, ?_, rfl, rfl, rfl⟩
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2.1, hr.2.2, ite_false]
+
+theorem Reset.pass {s t : State} (h : Reset s t) : t.mem.readW (off (t.gpr .rbp) 0) 64 = 0 := by
+ rw [h.mem, h.regs .rbp (by decide)]
+ exact Mem.readW_writeW_self64 _ _ _
+
+theorem Reset.frame {s t : State} (h : Reset s t) : Frame [⟨s.gpr .rbp, 8⟩] s.mem t.mem := by
+ rw [h.mem]
+ exact (Frame.refl _ _).writeW (r := ⟨s.gpr .rbp, 8⟩) (by simp) _
+ (by simpa only [off, BitVec.add_zero] using Region.contains_self (s.gpr .rbp) 8)
+
+theorem Reset.read {s t : State} (h : Reset s t) (d : Nat) (separate : 8 ≤ d) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [h.mem, h.regs .rbp (by decide)]
+ exact Mem.readW_writeW_sep (w := 64) (w' := 64)
+ (Offset.sep _ (d := d) (n := 8) (e := 0) (k := 8) (Or.inr (by omega)) (by omega) (by decide)) (by decide)
+
+end VG.Proof.Argon2.X86_64.FillSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlice.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlice.lean
new file mode 100644
index 000000000..d319837c1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlice.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlicePrepare
+
+/-! Correctness of one complete slice, starting at lane zero. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlice
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem code_ok (s : State) (p : Params) (pass slice : Nat) (h : Ready p pass slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillSlice.code s
+ (FillLanes.Finished s · p pass slice (Proof.Argon2.lanes p pass slice 0 p.lanes state)) := by
+ unfold Impl.Argon2.X86_64.FillSlice.code
+ refine WP.seq ((setup_ok s p pass slice h).mono ?_)
+ intro a prepared
+ have bp := prepared.keeps.regs .rbp (by decide)
+ have base : FillKernel.matrix a = FillKernel.matrix s := by unfold FillKernel.matrix; rw [prepared.keeps.mem, bp]
+ have work : AddressCalls.work a = AddressCalls.work s := by unfold AddressCalls.work; rw [prepared.keeps.mem, bp]
+ have representedA : Proof.Argon2.Represents a.mem (FillKernel.matrix a) p.blocks state.memory := by
+ rw [prepared.keeps.mem, base]; exact represented
+ refine (FillLanes.loop_ok p.lanes a p pass 0 slice prepared.ready state representedA
+ h.parameters.lanesPositive (by omega)).mono ?_
+ intro t finished
+ refine ⟨finished.represented, finished.matrix.trans base, finished.work.trans work, finished.laneWord,
+ finished.rd.trans prepared.keeps.rd, finished.wr.trans prepared.keeps.wr, ?_,
+ finished.mxcsr.trans prepared.keeps.mxcsr, ?_, finished.header⟩
+ · have frame := finished.frame
+ rw [FillBlock.writes, base, work, prepared.keeps.regs .rsp (by decide), bp, prepared.keeps.mem] at frame
+ exact frame
+ · intro r hr bx ix
+ have ne : r ∉ [Reg.rbx] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact bx
+ exact (finished.regs r hr bx ix).trans (prepared.keeps.regs r ne)
+
+end VG.Proof.Argon2.X86_64.FillSlice
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceAdvance.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceAdvance.lean
new file mode 100644
index 000000000..ac3331a18
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceAdvance.lean
@@ -0,0 +1,36 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSlices
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlice
+
+/-! Slice advancement retains the public header and matrix allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlices
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillSlices
+
+theorem advance_ok (s : State) : WP isa (.block advance) s fun t =>
+ t.gpr .r14 = s.gpr .r14 + 1 ∧ t.cf = decide ((s.gpr .r14 + 1).toNat < 4) ∧ Divide.Keeps [.r14] s t := by
+ apply WP.of_runBlock
+ simp only [advance, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, RegUpd.cf_arithFlags,
+ show BitVec.signExtend 64 (1 : BitVec 32) = (1 : Addr) from rfl,
+ show BitVec.signExtend 64 (4 : BitVec 32) = (4 : Addr) from rfl,
+ show (4 : Addr).toNat = 4 from rfl,
+ ite_true, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem advanced_header {s t : State} {p : Params} {pass lane slice : Nat}
+ (h : FillHeader.Ready p pass lane slice s) (k : Divide.Keeps [.r14] s t)
+ (value : t.gpr .r14 = BitVec.ofNat 64 (slice + 1)) : FillHeader.Ready p pass lane (slice + 1) t := by
+ obtain ⟨old, words⟩ := h.words
+ exact h.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact k.regs _ (by decide))
+ k.mem k.rd k.wr ((k.regs .rbx (by decide)).trans words.laneWord) value
+
+end VG.Proof.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceCT.lean
new file mode 100644
index 000000000..a6e651118
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSliceCT.lean
@@ -0,0 +1,59 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlice
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLanesCT
+
+/-! A complete slice exposes only its specified reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlice
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass slice : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ left : Ready p pass slice s
+ right : Ready p pass slice t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.lanes p pass slice 0 p.lanes leftState).indices =
+ (Proof.Argon2.lanes p pass slice 0 p.lanes rightState).indices
+
+theorem setup_trace : RelCT isa (fun _ _ : State => True) (.block Impl.Argon2.X86_64.FillSlice.setup) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+
+theorem setup_public_rel (p : Params) (pass slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass slice leftState rightState) (.block Impl.Argon2.X86_64.FillSlice.setup)
+ (FillLanes.Related p pass 0 slice p.lanes leftState rightState) := by
+ have trace := setup_trace.mono (P' := Related p pass slice leftState rightState)
+ (fun _ _ _ => trivial) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨setup_ok s p pass slice h.left, setup_ok t p pass slice h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨⟨ha.ready, hb.ready, ?_, ?_, ?_, ?_⟩, ?_, ?_, hp.indices⟩
+ · rw [ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]; exact hp.bases
+ · rw [ha.keeps.regs .rsp (by decide), hb.keeps.regs .rsp (by decide)]; exact hp.stacks
+ · unfold FillKernel.matrix
+ rw [ha.keeps.mem, hb.keeps.mem, ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]
+ exact hp.matrices
+ · unfold AddressCalls.work
+ rw [ha.keeps.mem, hb.keeps.mem, ha.keeps.regs .rbp (by decide), hb.keeps.regs .rbp (by decide)]
+ exact hp.work
+ · unfold FillKernel.matrix; rw [ha.keeps.mem, ha.keeps.regs .rbp (by decide)]; exact hp.leftMatrix
+ · unfold FillKernel.matrix; rw [hb.keeps.mem, hb.keeps.regs .rbp (by decide)]; exact hp.rightMatrix
+
+theorem code_rel (p : Params) (pass slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass slice leftState rightState) Impl.Argon2.X86_64.FillSlice.code (fun _ _ => True) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq setupA lanesA =>
+ cases eb with
+ | seq setupB lanesB =>
+ obtain ⟨setupTrace, related⟩ := setup_public_rel p pass slice leftState rightState _ _ _ _ _ _ hp setupA setupB
+ obtain ⟨lanesTrace, _⟩ := FillLanes.loop_rel p pass 0 slice p.lanes leftState rightState
+ hp.left.parameters.lanesPositive (by omega) _ _ _ _ _ _ related lanesA lanesB
+ exact ⟨by rw [setupTrace, lanesTrace], trivial⟩
+
+end VG.Proof.Argon2.X86_64.FillSlice
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicePrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicePrepare.lean
new file mode 100644
index 000000000..50b555c29
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicePrepare.lean
@@ -0,0 +1,31 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillSlice
+import VerifiedGarbage.Proof.Argon2.X86_64.FillHeader
+
+/-! Initialize lane zero without requiring a valid incoming lane coordinate. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlice
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (pass slice : Nat) (s : State) : Prop where
+ parameters : FillContext.Parameters p pass 0 slice
+ header : ∃ lane, FillHeader.Ready p pass lane slice s
+
+structure Prepared (s t : State) (p : Params) (pass slice : Nat) : Prop where
+ ready : SegmentSetup.Ready p pass 0 slice t
+ keeps : Divide.Keeps [.rbx] s t
+
+theorem setup_ok (s : State) (p : Params) (pass slice : Nat) (h : Ready p pass slice s) :
+ WP isa (.block Impl.Argon2.X86_64.FillSlice.setup) s (Prepared s · p pass slice) := by
+ refine (SegmentSetup.register_ok s .rbx 0).mono ?_
+ rintro t ⟨laneWord, keeps⟩
+ obtain ⟨lane, header⟩ := h.header
+ obtain ⟨old, words⟩ := header.words
+ have next : FillHeader.Ready p pass 0 slice t := header.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide))
+ keeps.mem keeps.rd keeps.wr laneWord ((keeps.regs .r14 (by decide)).trans words.sliceWord)
+ exact ⟨next.segment h.parameters, keeps⟩
+
+end VG.Proof.Argon2.X86_64.FillSlice
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlices.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlices.lean
new file mode 100644
index 000000000..87f40a5b6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlices.lean
@@ -0,0 +1,65 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlicesBody
+import VerifiedGarbage.Proof.Argon2.Slices
+
+/-! Termination and correctness of the four-slice filling pass. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlices
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Finished (s t : State) (p : Params) (pass : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ header : FillHeader.Ready p pass p.lanes 4 t
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r14 → r ≠ .r15 → t.gpr r = s.gpr r
+
+theorem Done.finished {s t : State} {p : Params} {pass slice : Nat} {state : FillState}
+ (h : Done s t p pass slice state) (last : slice + 1 = 4) :
+ Finished s t p pass (Proof.Argon2.lanes p pass slice 0 p.lanes state) :=
+ ⟨h.represented, h.matrix, h.work, last ▸ h.header, h.rd, h.wr, h.frame, h.mxcsr, h.regs⟩
+
+theorem Finished.prepend {s a t : State} {p : Params} {pass slice : Nat} {state finalState : FillState}
+ (first : Done s a p pass slice state) (rest : Finished a t p pass finalState) : Finished s t p pass finalState := by
+ refine ⟨rest.represented, rest.matrix.trans first.matrix, rest.work.trans first.work, rest.header,
+ rest.rd.trans first.rd, rest.wr.trans first.wr, ?_, rest.mxcsr.trans first.mxcsr, ?_⟩
+ · have frame := rest.frame
+ rw [FillBlock.writes, first.matrix, first.work,
+ first.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide),
+ first.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)] at frame
+ exact first.frame.trans frame
+ · intro r hr bx sl ix; exact (rest.regs r hr bx sl ix).trans (first.regs r hr bx sl ix)
+
+theorem loop_ok (count : Nat) (s : State) (p : Params) (pass slice : Nat)
+ (h : FillSlice.Ready p pass slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (positive : 0 < count) (endSlice : slice + count = 4) :
+ WP isa Impl.Argon2.X86_64.FillSlices.loop s (Finished s · p pass (Proof.Argon2.slices p pass slice count state)) := by
+ induction count generalizing s slice state with
+ | zero => omega
+ | succ n ih =>
+ obtain ⟨trace, a, run, done⟩ := body_ok s p pass slice h state represented
+ rw [Proof.Argon2.slices_succ]
+ cases n with
+ | zero =>
+ have last : slice + 1 = 4 := endSlice
+ refine ⟨_, a, .loopExit run ?_, done.finished last⟩
+ simp only [eval, done.cf, last, Nat.lt_irrefl, decide_false]
+ | succ n =>
+ have active : slice + 1 < 4 := by omega
+ obtain ⟨restTrace, t, restRun, finished⟩ := ih a (slice + 1) (done.next active)
+ (Proof.Argon2.lanes p pass slice 0 p.lanes state) done.represented (by omega) (by omega)
+ refine ⟨_, t, .loopNext run ?_ restRun, finished.prepend done⟩
+ simp only [eval, done.cf, active, decide_true]
+
+theorem pass_ok (s : State) (p : Params) (pass : Nat) (h : FillSlice.Ready p pass 0 s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillSlices.loop s (Finished s · p pass (fillPass p state pass)) := by
+ rw [← Proof.Argon2.slices_pass p pass state]
+ exact loop_ok 4 s p pass 0 h state represented (by decide) (by decide)
+
+end VG.Proof.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBody.lean
new file mode 100644
index 000000000..0c65e938c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBody.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSliceAdvance
+
+/-! Fill one slice and advance its public coordinate. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlices
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Done (s t : State) (p : Params) (pass slice : Nat) (state : FillState) : Prop where
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks
+ (Proof.Argon2.lanes p pass slice 0 p.lanes state).memory
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ header : FillHeader.Ready p pass p.lanes (slice + 1) t
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (FillBlock.writes s p) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → r ≠ .r14 → r ≠ .r15 → t.gpr r = s.gpr r
+ cf : t.cf = decide (slice + 1 < 4)
+ next : slice + 1 < 4 → FillSlice.Ready p pass (slice + 1) t
+
+theorem body_ok (s : State) (p : Params) (pass slice : Nat) (h : FillSlice.Ready p pass slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.FillSlices.body s (Done s · p pass slice state) := by
+ unfold Impl.Argon2.X86_64.FillSlices.body
+ refine WP.seq ((FillSlice.code_ok s p pass slice h state represented).mono ?_)
+ intro a filled
+ have header := FillHeader.of_lanes_finished filled
+ obtain ⟨old, words⟩ := header.words
+ refine (advance_ok a).mono ?_
+ rintro t ⟨value, flag, keeps⟩
+ have added : a.gpr .r14 + 1 = BitVec.ofNat 64 (slice + 1) := by
+ rw [words.sliceWord, BitVec.ofNat_add]; rfl
+ have nextWord := value.trans added
+ have nextHeader := advanced_header header keeps nextWord
+ have bp := keeps.regs .rbp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix a := by unfold FillKernel.matrix; rw [keeps.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work a := by unfold AddressCalls.work; rw [keeps.mem, bp]
+ have sliceBound := h.parameters.sliceBound
+ refine ⟨?_, base.trans filled.matrix, work.trans filled.work, nextHeader, keeps.rd.trans filled.rd,
+ keeps.wr.trans filled.wr, ?_, keeps.mxcsr.trans filled.mxcsr, ?_, ?_, ?_⟩
+ · rw [keeps.mem, base]; exact filled.represented
+ · rw [keeps.mem]; exact filled.frame
+ · intro r hr bx sl ix
+ have outside : r ∉ [Reg.r14] := by simp only [List.mem_cons, List.not_mem_nil, or_false]; exact sl
+ exact (keeps.regs r outside).trans (filled.regs r hr bx ix)
+ · rw [flag, added, ReferenceMap.word_nat (slice + 1) (by omega)]
+ · intro active
+ exact ⟨{ h.parameters with sliceBound := active }, p.lanes, nextHeader⟩
+
+end VG.Proof.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBodyCT.lean
new file mode 100644
index 000000000..3dcb93464
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesBodyCT.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlicesBody
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSliceCT
+
+/-! A slice iteration preserves public allocations and its public continuation guard. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlices
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillSlices
+
+theorem advance_rel : RelCT isa (fun _ _ : State => True) (.block advance) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+
+structure NextRelated (p : Params) (pass slice : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ left : FillSlice.Ready p pass slice s
+ right : FillSlice.Ready p pass slice t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+
+theorem body_rel (p : Params) (pass slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (FillSlice.Related p pass slice leftState rightState) body
+ (fun s t => s.cf = t.cf ∧ (slice + 1 < 4 → NextRelated p pass (slice + 1)
+ (Proof.Argon2.lanes p pass slice 0 p.lanes leftState) (Proof.Argon2.lanes p pass slice 0 p.lanes rightState) s t)) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq sliceA advanceA =>
+ cases eb with
+ | seq sliceB advanceB =>
+ obtain ⟨sliceTrace, _⟩ := FillSlice.code_rel p pass slice leftState rightState _ _ _ _ _ _ hp sliceA sliceB
+ obtain ⟨advanceTrace, _⟩ := advance_rel _ _ _ _ _ _ trivial advanceA advanceB
+ obtain ⟨_, a', runA, doneA⟩ := body_ok s p pass slice hp.left leftState hp.leftMatrix
+ obtain ⟨_, b', runB, doneB⟩ := body_ok t p pass slice hp.right rightState hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det (.seq sliceA advanceA) runA
+ obtain ⟨_, rfl⟩ := Exec.det (.seq sliceB advanceB) runB
+ refine ⟨by rw [sliceTrace, advanceTrace], doneA.cf.trans doneB.cf.symm, ?_⟩
+ intro active
+ refine ⟨doneA.next active, doneB.next active, ?_, ?_,
+ doneA.matrix.trans (hp.matrices.trans doneB.matrix.symm),
+ doneA.work.trans (hp.work.trans doneB.work.symm), doneA.represented, doneB.represented⟩
+ · exact (doneA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm)
+ · exact (doneA.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans
+ (hp.stacks.trans (doneB.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesCT.lean
new file mode 100644
index 000000000..78da1ac47
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillSlicesCT.lean
@@ -0,0 +1,59 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlices
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSlicesBodyCT
+import VerifiedGarbage.Proof.Argon2.SlicesIndices
+
+/-! The complete pass leaks only its reviewed reference log, including Argon2id's mode change. -/
+
+namespace VG.Proof.Argon2.X86_64.FillSlices
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (pass slice count : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ states : NextRelated p pass slice leftState rightState s t
+ indices : (Proof.Argon2.slices p pass slice count leftState).indices =
+ (Proof.Argon2.slices p pass slice count rightState).indices
+
+theorem loop_rel (p : Params) (pass slice count : Nat) (leftState rightState : FillState)
+ (positive : 0 < count) (endSlice : slice + count = 4) :
+ RelCT isa (Related p pass slice count leftState rightState) Impl.Argon2.X86_64.FillSlices.loop (fun _ _ => True) := by
+ let I := fun n s t => ∃ (slice : Nat) (leftState rightState : FillState),
+ slice + n = 4 ∧ 0 < n ∧ Related p pass slice n leftState rightState s t
+ have steps : ∀ n, RelCT isa (I n) Impl.Argon2.X86_64.FillSlices.body fun s t =>
+ isa.eval .b s = isa.eval .b t ∧ (isa.eval .b s = some false → True) ∧
+ (isa.eval .b s = some true → ∃ m < n, I m s t) := by
+ intro n s t ts tt a b hp ea eb
+ obtain ⟨j, ls, rs, endSlice, positive, hp⟩ := hp
+ cases n with
+ | zero => omega
+ | succ n =>
+ have sliceRelated : FillSlice.Related p pass j ls rs s t :=
+ ⟨hp.states.left, hp.states.right, hp.states.bases, hp.states.stacks, hp.states.matrices, hp.states.work,
+ hp.states.leftMatrix, hp.states.rightMatrix, Proof.Argon2.slices_first_lane_fold p pass j n ls rs
+ hp.states.left.parameters.segment_bound.1 hp.indices⟩
+ obtain ⟨trace, flags, next⟩ := body_rel p pass j ls rs _ _ _ _ _ _ sliceRelated ea eb
+ obtain ⟨_, a', runA, done⟩ := body_ok s p pass j hp.states.left ls hp.states.leftMatrix
+ obtain ⟨_, rfl⟩ := Exec.det ea runA
+ refine ⟨trace, ?_, fun _ => trivial, ?_⟩
+ · simp only [eval, flags]
+ · intro taken
+ have active : j + 1 < 4 := by
+ simp only [eval, done.cf, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ have indices := hp.indices
+ rw [Proof.Argon2.slices_succ, Proof.Argon2.slices_succ] at indices
+ exact ⟨n, by omega, j + 1, Proof.Argon2.lanes p pass j 0 p.lanes ls,
+ Proof.Argon2.lanes p pass j 0 p.lanes rs, by omega, by omega, next active, indices⟩
+ exact (RelCT.loop I steps count).mono
+ (fun _ _ h => ⟨slice, leftState, rightState, endSlice, positive, h⟩) (fun _ _ h => h)
+
+theorem pass_rel (p : Params) (pass : Nat) (leftState rightState : FillState) :
+ RelCT isa (fun s t => NextRelated p pass 0 leftState rightState s t ∧
+ (fillPass p leftState pass).indices = (fillPass p rightState pass).indices)
+ Impl.Argon2.X86_64.FillSlices.loop (fun _ _ => True) := by
+ refine (loop_rel p pass 0 4 leftState rightState (by decide) (by decide)).mono ?_ (fun _ _ h => h)
+ intro s t h
+ refine ⟨h.1, ?_⟩
+ rw [Proof.Argon2.slices_pass p pass leftState, Proof.Argon2.slices_pass p pass rightState]
+ exact h.2
+
+end VG.Proof.Argon2.X86_64.FillSlices
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWrite.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWrite.lean
new file mode 100644
index 000000000..ba86b63da
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWrite.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWritePrefix
+import VerifiedGarbage.Proof.Argon2.X86_64.CountCandidates
+
+/-! Whole-block first-pass copying and later-pass XOR, with a frame proof. -/
+
+namespace VG.Proof.Argon2.X86_64.FillWrite
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillWrite
+
+theorem code_ok (s : State)
+ (hs : (⟨s.gpr .rsi, 1024⟩ : Region) ∈ s.rd ++ s.wr)
+ (hw : (⟨s.gpr .rdi, 1024⟩ : Region) ∈ s.wr)
+ (hd : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩) :
+ WP isa code s fun t =>
+ blockAt t.mem (s.gpr .rdi) =
+ (if s.gpr .r9 = 0 then blockAt s.mem (s.gpr .rsi)
+ else xorBlock (blockAt s.mem (s.gpr .rsi)) (blockAt s.mem (s.gpr .rdi))) ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ unfold code
+ refine WP.seq ((CountCandidates.compare_ok s).mono ?_)
+ rintro a ⟨flag, ka⟩
+ have src : a.gpr .rsi = s.gpr .rsi := ka.regs .rsi (by simp)
+ have dest : a.gpr .rdi = s.gpr .rdi := ka.regs .rdi (by simp)
+ have hs' : (⟨a.gpr .rsi, 1024⟩ : Region) ∈ a.rd ++ a.wr := by
+ rw [src, ka.rd, ka.wr]; exact hs
+ have hw' : (⟨a.gpr .rdi, 1024⟩ : Region) ∈ a.wr := by
+ rw [dest, ka.wr]; exact hw
+ have hd' : (⟨a.gpr .rsi, 1024⟩ : Region).Disjoint ⟨a.gpr .rdi, 1024⟩ := by
+ rw [src, dest]; exact hd
+ refine WP.ite (decide (s.gpr .r9 = 0)) (by simp only [eval, flag]) ?_ ?_
+ · intro h
+ have zero := of_decide_eq_true h
+ refine (prefix_ok false 128 (by decide) a hs' hw' hd').mono ?_
+ rintro t ⟨written, frame, keeps, mx⟩
+ refine ⟨?_, ?_, ?_, mx.trans ka.mxcsr⟩
+ · rw [dest] at written
+ rw [ite_eq_left zero, written_block written]
+ simp only [result, Bool.false_eq_true, ite_false, ka.mem, src]
+ · rw [dest, ka.mem] at frame; exact frame
+ · exact (show CopyKeeps s a from ⟨fun r _ => ka.regs r (by simp), ka.rd, ka.wr⟩).trans keeps
+ · intro h
+ have nonzero := of_decide_eq_false h
+ refine (prefix_ok true 128 (by decide) a hs' hw' hd').mono ?_
+ rintro t ⟨written, frame, keeps, mx⟩
+ refine ⟨?_, ?_, ?_, mx.trans ka.mxcsr⟩
+ · rw [dest] at written
+ rw [ite_eq_right nonzero, written_block written]
+ simp only [result, ite_true, ka.mem, src]
+ · rw [dest, ka.mem] at frame; exact frame
+ · exact (show CopyKeeps s a from ⟨fun r _ => ka.regs r (by simp), ka.rd, ka.wr⟩).trans keeps
+
+end VG.Proof.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCT.lean
new file mode 100644
index 000000000..0f85f3962
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCT.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWriteLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Both write paths have a public, fixed sequence of memory accesses. -/
+
+namespace VG.Proof.Argon2.X86_64.FillWrite
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillWrite
+
+theorem code_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.r9, .rdi, .rsi], s.gpr r = t.gpr r) code
+ (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.r9, .rdi, .rsi])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCover.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCover.lean
new file mode 100644
index 000000000..2da771373
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteCover.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWrite
+import VerifiedGarbage.Proof.Framework.X86_64.Inline
+
+/-! Use block writes in a matrix allocation with larger permission regions. -/
+
+namespace VG.Proof.Argon2.X86_64.FillWrite
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillWrite
+
+theorem code_cover_ok (s : State)
+ (hs : Covers [⟨s.gpr .rsi, 1024⟩] (s.rd ++ s.wr))
+ (hw : Covers [⟨s.gpr .rdi, 1024⟩] s.wr)
+ (hd : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩) :
+ WP isa code s fun t =>
+ blockAt t.mem (s.gpr .rdi) =
+ (if s.gpr .r9 = 0 then blockAt s.mem (s.gpr .rsi)
+ else xorBlock (blockAt s.mem (s.gpr .rsi)) (blockAt s.mem (s.gpr .rdi))) ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ let a := s.withRegions [⟨s.gpr .rsi, 1024⟩] [⟨s.gpr .rdi, 1024⟩]
+ obtain ⟨tr, t, he, value, frame, keeps, mx⟩ := code_ok a (by simp [a]) (by simp [a]) hd
+ have cover : Covers (a.rd ++ a.wr) (s.rd ++ s.wr) := by
+ intro p n ⟨r, hr, hc⟩
+ change r ∈ [⟨s.gpr .rsi, 1024⟩, ⟨s.gpr .rdi, 1024⟩] at hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact hs p n ⟨_, by simp, hc⟩
+ · obtain ⟨r, hr, hc⟩ := hw p n ⟨_, by simp, hc⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ have he' := Exec.widen (rd := s.rd) (wr := s.wr) he cover hw
+ simp only [a, State.withRegions_withRegions, State.withRegions_self] at he'
+ refine ⟨tr, t.withRegions s.rd s.wr, he', value, frame, ?_, mx⟩
+ exact ⟨keeps.1, rfl, rfl⟩
+
+end VG.Proof.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteLit.lean
new file mode 100644
index 000000000..d869cc860
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.FillWrite
+
+/-! Checked literal of the complete copy/XOR block write. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.FillWrite.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWritePrefix.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWritePrefix.lean
new file mode 100644
index 000000000..71ba91432
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWritePrefix.lean
@@ -0,0 +1,90 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWriteWord
+import VerifiedGarbage.Proof.Argon2.X86_64.Finish
+
+/-! Compose the word writes without re-executing a long load/store block. -/
+
+namespace VG.Proof.Argon2.X86_64.FillWrite
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.FillWrite
+
+def result (xorOld : Bool) (m : Mem) (src dest : Addr) : Block :=
+ if xorOld then xorBlock (blockAt m src) (blockAt m dest) else blockAt m src
+
+theorem result_get (xorOld : Bool) (m : Mem) (src dest : Addr) (i : Fin 128) :
+ (result xorOld m src dest)[i] = value xorOld m src dest i.val := by
+ cases xorOld <;> simp only [result, value, Bool.false_eq_true, ite_false, ite_true,
+ xorBlock_get, blockAt_get]
+
+theorem frame_extend {m m' : Mem} {dest : Addr} {n k : Nat}
+ (hf : Frame [⟨dest, 8 * n⟩] m m') (h : n ≤ k) : Frame [⟨dest, 8 * k⟩] m m' := by
+ apply hf.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨_, by simp, Region.sub_prefix (Nat.mul_le_mul_left 8 h)⟩
+
+theorem source_read {m m' : Mem} {src dest : Addr} {n : Nat}
+ (hf : Frame [⟨dest, 8 * n⟩] m m') (hn : n ≤ 128)
+ (hd : (⟨src, 1024⟩ : Region).Disjoint ⟨dest, 1024⟩) (i : Fin 128) :
+ m'.readW (off src (8 * i.val)) 64 = m.readW (off src (8 * i.val)) 64 := by
+ have full := frame_extend hf hn
+ exact full.readW (r := ⟨src, 1024⟩)
+ (Offset.contains_base src (by omega) (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r; exact hd) (by decide)
+
+theorem old_read {m m' : Mem} {dest : Addr} {n : Nat}
+ (hf : Frame [⟨dest, 8 * n⟩] m m') (hn : n < 128) :
+ m'.readW (off dest (8 * n)) 64 = m.readW (off dest (8 * n)) 64 :=
+ hf.readW (r := ⟨off dest (8 * n), 8⟩) (Region.contains_self _ _)
+ (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact Offset.disjoint_base dest (Nat.le_refl _) (by omega)) (by decide)
+
+theorem prefix_ok (xorOld : Bool) (n : Nat) (hn : n ≤ 128) (s : State)
+ (hs : (⟨s.gpr .rsi, 1024⟩ : Region) ∈ s.rd ++ s.wr)
+ (hw : (⟨s.gpr .rdi, 1024⟩ : Region) ∈ s.wr)
+ (hd : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩) :
+ WP isa (.block (words xorOld n)) s fun t =>
+ Written t.mem (s.gpr .rdi) (result xorOld s.mem (s.gpr .rsi) (s.gpr .rdi)) n ∧
+ Frame [⟨s.gpr .rdi, 8 * n⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ induction n with
+ | zero => exact WP.block_nil ⟨fun i hi => by omega, Frame.refl _ _, CopyKeeps.refl s, rfl⟩
+ | succ n ih =>
+ simp only [words, List.range_succ, List.flatMap_append, List.flatMap_cons,
+ List.flatMap_nil, List.append_nil]
+ apply WP.block_append
+ refine (ih (by omega)).mono ?_
+ rintro t ⟨written, frame, keeps, mx⟩
+ have hn' : n < 128 := by omega
+ have src : t.gpr .rsi = s.gpr .rsi := keeps.1 .rsi (by decide)
+ have dest : t.gpr .rdi = s.gpr .rdi := keeps.1 .rdi (by decide)
+ have write : InRegions t.wr (off (t.gpr .rdi) (8 * n)) 8 := by
+ rw [dest, keeps.2.2]
+ exact ⟨_, hw, Offset.contains_base _ (by omega) (by omega)⟩
+ have read : InRegions (t.rd ++ t.wr) (off (t.gpr .rsi) (8 * n)) 8 := by
+ rw [src, keeps.2.1, keeps.2.2]
+ exact ⟨_, hs, Offset.contains_base _ (by omega) (by omega)⟩
+ have old : InRegions (t.rd ++ t.wr) (off (t.gpr .rdi) (8 * n)) 8 := by
+ obtain ⟨r, hr, hc⟩ := write
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ refine (word_ok xorOld t n read write old).mono ?_
+ rintro u ⟨mem, regs, rd, wr, mx'⟩
+ have v : value xorOld t.mem (t.gpr .rsi) (t.gpr .rdi) n =
+ (result xorOld s.mem (s.gpr .rsi) (s.gpr .rdi))[(⟨n, hn'⟩ : Fin 128)] := by
+ rw [result_get, src, dest]
+ unfold value
+ rw [source_read frame (by omega) hd ⟨n, hn'⟩]
+ cases xorOld
+ · rfl
+ · rw [old_read frame hn']
+ refine ⟨?_, ?_, keeps.trans ⟨regs, rd, wr⟩, mx'.trans mx⟩
+ · rw [mem, v, dest]
+ exact written_step hn' written
+ · rw [mem, dest]
+ exact (frame_extend frame (Nat.le_succ n)).writeW
+ (r := ⟨s.gpr .rdi, 8 * (n + 1)⟩) (by simp) _
+ (Offset.contains_base _ (by omega) (by omega))
+
+end VG.Proof.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteWord.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteWord.lean
new file mode 100644
index 000000000..ce2f39452
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FillWriteWord.lean
@@ -0,0 +1,40 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FillWrite
+import VerifiedGarbage.Proof.Argon2.X86_64.Memory
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! One output word, keeping register writes folded during execution. -/
+
+namespace VG.Proof.Argon2.X86_64.FillWrite
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FillWrite
+
+def value (xorOld : Bool) (m : Mem) (src dest : Addr) (i : Nat) : Addr :=
+ let next := m.readW (off src (8 * i)) 64
+ if xorOld then next ^^^ m.readW (off dest (8 * i)) 64 else next
+
+/-- The source is readable and the destination writable; its old contents
+are read only on later passes. -/
+theorem word_ok (xorOld : Bool) (s : State) (i : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rsi) (8 * i)) 8)
+ (hw : InRegions s.wr (off (s.gpr .rdi) (8 * i)) 8)
+ (ho : InRegions (s.rd ++ s.wr) (off (s.gpr .rdi) (8 * i)) 8) :
+ WP isa (.block (Impl.Argon2.X86_64.FillWrite.word xorOld i)) s fun t =>
+ t.mem = s.mem.writeW (off (s.gpr .rdi) (8 * i))
+ (value xorOld s.mem (s.gpr .rsi) (s.gpr .rdi) i) ∧
+ (∀ r, r ≠ .rax → t.gpr r = s.gpr r) ∧
+ t.rd = s.rd ∧ t.wr = s.wr ∧ t.mxcsr = s.mxcsr := by
+ cases xorOld <;> apply WP.of_runBlock <;>
+ simp only [Impl.Argon2.X86_64.FillWrite.word, value, Bool.false_eq_true, ite_false, ite_true,
+ List.cons_append, List.nil_append,
+ runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ State.load64, State.store64, execAlu, ea_at, hr, hw, ho,
+ RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ RegUpd.gpr_arithFlags, RegUpd.mem_arithFlags, RegUpd.rd_arithFlags,
+ RegUpd.wr_arithFlags, reduceCtorEq, ite_true, ite_false,
+ Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ all_goals
+ refine ⟨trivial, ?_, trivial, trivial, rfl⟩
+ intro r hr
+ simp only [hr, ite_false]
+
+end VG.Proof.Argon2.X86_64.FillWrite
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCall.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCall.lean
new file mode 100644
index 000000000..e2fc8dfec
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCall.lean
@@ -0,0 +1,94 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitCall
+
+/-! The final H′ call accepts a complete reduced block and a public tag length. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalCall
+
+open VG VG.X86_64
+open VG.Impl.Argon2.X86_64.HPrime (code)
+open VG.Spec.Blake2 (bytesAt)
+
+structure CallReady (len : Nat) (s : State) : Prop where
+ positive : 1 ≤ len
+ bound : len < 2 ^ 32
+ input : Covers [⟨s.gpr .rdi, 1024⟩] (s.rd ++ s.wr)
+ output : Covers [⟨s.gpr .rdx, len⟩] s.wr
+ work : (⟨s.gpr .r8, 16384⟩ : Region) ∈ s.wr
+ inputWork : (⟨s.gpr .rdi, 1024⟩ : Region).Disjoint ⟨s.gpr .r8, 16384⟩
+ outputWork : (⟨s.gpr .rdx, len⟩ : Region).Disjoint ⟨s.gpr .r8, 16384⟩
+ stackInput : (below (s.gpr .rsp) 24).Disjoint ⟨s.gpr .rdi, 1024⟩
+ stackOutput : (below (s.gpr .rsp) 24).Disjoint ⟨s.gpr .rdx, len⟩
+ stackWork : (below (s.gpr .rsp) 24).Disjoint ⟨s.gpr .r8, 16384⟩
+
+structure Called (len : Nat) (s t : State) : Prop where
+ digest : bytesAt t.mem (s.gpr .rdx) len = Spec.Argon2.hPrime len (bytesAt s.mem (s.gpr .rdi) 1024)
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨s.gpr .rdx, len⟩, ⟨s.gpr .r8, 16384⟩, below (s.gpr .rsp) 24] s.mem t.mem
+
+theorem hPrime_call_hyps (len : Nat) (s : State) (h : CallReady len s)
+ (inputLength : s.gpr .rsi = 1024) (outputLength : s.gpr .rcx = BitVec.ofNat 64 len) :
+ HPrime.localContract.pre (s.callEntry.withRegions [⟨s.gpr .rdi, 1024⟩]
+ [⟨s.gpr .rdx, len⟩, ⟨s.gpr .r8, 16384⟩]) ∧
+ Covers [⟨s.gpr .rdi, 1024⟩, ⟨s.gpr .rdx, len⟩, ⟨s.gpr .r8, 16384⟩] (s.rd ++ s.wr) ∧
+ Covers [⟨s.gpr .rdx, len⟩, ⟨s.gpr .r8, 16384⟩] s.wr := by
+ have length : (BitVec.ofNat 64 len).toNat = len := by
+ rw [BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans h.bound (by decide))]
+ have g : ∀ r, r ≠ .rsp → s.callEntry.gpr r = s.gpr r := fun r hr => State.callEntry_gpr s hr
+ have inner := below_callee (s.gpr .rsp) 16
+ have ret := below_sub (sp := s.gpr .rsp) (by decide : 8 ≤ 24) (by decide)
+ refine ⟨?_, ?_, ?_⟩
+ · simp only [HPrime.localContract, HPrime.inputR, HPrime.outputR, HPrime.workR,
+ HPrime.stackR, HPrime.retR, State.withRegions_gpr, State.withRegions_rd,
+ State.withRegions_wr, g _ (by decide : Reg.rdi ≠ .rsp),
+ g _ (by decide : Reg.rsi ≠ .rsp), g _ (by decide : Reg.rdx ≠ .rsp),
+ g _ (by decide : Reg.rcx ≠ .rsp), g _ (by decide : Reg.r8 ≠ .rsp),
+ State.callEntry_rsp, inputLength, outputLength, length]
+ exact ⟨rfl, trivial, by decide, h.positive, h.bound, h.inputWork, h.outputWork,
+ h.stackInput.sub_left inner, h.stackOutput.sub_left inner, h.stackWork.sub_left inner,
+ h.stackOutput.sub_left ret, h.stackWork.sub_left ret⟩
+ · intro p n hp
+ rcases hp with ⟨r, hr, hc⟩
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact h.input p n ⟨_, List.mem_singleton_self _, hc⟩
+ · obtain ⟨r, hr, hc⟩ := h.output p n ⟨_, List.mem_singleton_self _, hc⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ · exact ⟨_, List.mem_append_right _ h.work, hc⟩
+ · intro p n ⟨r, hr, hc⟩
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.output p n ⟨_, List.mem_singleton_self _, hc⟩
+ · exact ⟨_, h.work, hc⟩
+
+theorem hPrime_call_ok (v : Proof.Blake2.X86_64.Backend) (name : String)
+ (len : Nat) (s : State) (h : CallReady len s) (inputLength : s.gpr .rsi = 1024)
+ (outputLength : s.gpr .rcx = BitVec.ofNat 64 len) :
+ WP isa (.call name (code (HPrime.hash v))) s (Called len s) := by
+ obtain ⟨pre, cover, writes⟩ := hPrime_call_hyps len s h inputLength outputLength
+ refine WP.call (k := HPrime.localContract) (HPrime.code_correct v) (MemoryInit.hPrime_nosp v)
+ (by rw [MemoryInit.hPrime_depth]; decide) pre cover writes ?_
+ intro t rd wr regs frame _ ⟨u, memU, regsU, digest⟩
+ have inputBytes : bytesAt s.callEntry.mem (s.gpr .rdi) 1024 = bytesAt s.mem (s.gpr .rdi) 1024 := by
+ apply Proof.Blake2.bytesAt_congr
+ intro i hi
+ exact Proof.MdStream.X86_64.callEntry_byte s (h.stackInput.sub_left
+ (below_sub (by decide) (by decide))) (show (1024 : Nat) ≤ 2 ^ 64 from by decide) hi
+ refine ⟨?_, regs, rd, wr, ?_⟩
+ · change bytesAt u.mem (s.callEntry.gpr .rdx) (s.callEntry.gpr .rcx).toNat =
+ Spec.Argon2.hPrime (s.callEntry.gpr .rcx).toNat
+ (bytesAt s.callEntry.mem (s.callEntry.gpr .rdi) (s.callEntry.gpr .rsi).toNat) at digest
+ rw [State.callEntry_gpr _ (by decide : Reg.rdx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rcx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rdi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rsi ≠ .rsp),
+ memU, inputLength, outputLength,
+ show (1024 : Addr).toNat = 1024 from rfl,
+ show (BitVec.ofNat 64 len).toNat = len from by
+ rw [BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans h.bound (by decide))], inputBytes] at digest
+ exact digest
+ · rw [MemoryInit.hPrime_depth] at frame
+ exact frame
+
+end VG.Proof.Argon2.X86_64.FinalCall
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCallCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCallCT.lean
new file mode 100644
index 000000000..8b1a0b1ca
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalCallCT.lean
@@ -0,0 +1,36 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalCall
+
+/-! # The final H′ call leak only their public argument registers -/
+
+namespace VG.Proof.Argon2.X86_64.FinalCall
+
+open VG VG.X86_64
+open VG.Impl.Argon2.X86_64.HPrime (code)
+
+theorem hPrime_call_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (len : Nat)
+ {P : State → State → Prop}
+ (pre : ∀ s t, P s t → CallReady len s ∧ CallReady len t ∧
+ s.gpr .rsi = 1024 ∧ t.gpr .rsi = 1024 ∧ s.gpr .rcx = BitVec.ofNat 64 len ∧ t.gpr .rcx = BitVec.ofNat 64 len ∧
+ s.gpr .rdi = t.gpr .rdi ∧ s.gpr .rdx = t.gpr .rdx ∧
+ s.gpr .r8 = t.gpr .r8 ∧ s.gpr .rsp = t.gpr .rsp) :
+ RelCT isa P (.call name (code (HPrime.hash v))) (fun _ _ => True) := by
+ apply RelCT.callEx (k := HPrime.localContract) (HPrime.code_correct v) (HPrime.code_ct v)
+ intro s t hp
+ obtain ⟨hs, ht, ls, lt, os, ot, di, dx, r8, sp⟩ := pre s t hp
+ obtain ⟨ps, cs, ws⟩ := hPrime_call_hyps len s hs ls os
+ obtain ⟨pt, ct, wt⟩ := hPrime_call_hyps len t ht lt ot
+ refine ⟨_, _, _, _, ps, pt, ?_, cs, ws, ct, wt, sp⟩
+ change s.callEntry.gpr .rdi = t.callEntry.gpr .rdi ∧
+ s.callEntry.gpr .rsi = t.callEntry.gpr .rsi ∧
+ s.callEntry.gpr .rdx = t.callEntry.gpr .rdx ∧
+ s.callEntry.gpr .rcx = t.callEntry.gpr .rcx ∧
+ s.callEntry.gpr .r8 = t.callEntry.gpr .r8 ∧
+ s.callEntry.gpr .rsp = t.callEntry.gpr .rsp
+ simp only [State.callEntry_gpr _ (by decide : Reg.rdi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rsi ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rdx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.rcx ≠ .rsp),
+ State.callEntry_gpr _ (by decide : Reg.r8 ≠ .rsp), State.callEntry_rsp]
+ exact ⟨di, ls.trans lt.symm, dx, os.trans ot.symm, r8, congrArg (· - 8) sp⟩
+
+end VG.Proof.Argon2.X86_64.FinalCall
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutput.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutput.lean
new file mode 100644
index 000000000..2ccc2be5a
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutput.lean
@@ -0,0 +1,41 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutputReady
+import VerifiedGarbage.Proof.Argon2.Serialization
+
+/-! The generic H′ call produces exactly the reviewed final Argon2 tag. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalOutput
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Spec.Blake2 (bytesAt)
+
+structure Done (s t : State) (p : Params) (memory : Array Block) : Prop where
+ digest : bytesAt t.mem (output s) p.tagLen = finish p memory
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨output s, p.tagLen⟩, ⟨work s, 16384⟩, below (s.gpr .rsp) 24] s.mem t.mem
+
+theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (p : Params)
+ (h : Ready p s) (memory : Array Block)
+ (block : blockAt s.mem (ReductionState.matrix s) = Proof.Argon2.reduction p memory 0 p.lanes zeroBlock) :
+ WP isa (Impl.Argon2.X86_64.FinalOutput.code name (HPrime.hash v)) s (Done s · p memory) := by
+ unfold Impl.Argon2.X86_64.FinalOutput.code
+ refine WP.seq ((args_ok s h.reads).mono ?_)
+ intro a args
+ have length := args.outputLength.trans h.tagWord
+ refine (FinalCall.hPrime_call_ok v name p.tagLen a (args.ready h) args.inputLength length).mono ?_
+ intro t called
+ refine ⟨?_, fun r hr => (called.regs r hr).trans (args.regs r hr), called.rd.trans args.keeps.rd,
+ called.wr.trans args.keeps.wr, ?_⟩
+ · have input : bytesAt a.mem (a.gpr .rdi) 1024 =
+ serialize (Proof.Argon2.reduction p memory 0 p.lanes zeroBlock) := by
+ rw [args.keeps.mem, args.input, ← Proof.Argon2.serialize_blockAt, block]
+ rw [Proof.Argon2.finish_reduction]
+ have digest := called.digest
+ rw [args.output, input] at digest
+ exact digest
+ · have frame := called.frame
+ rw [args.output, args.work, args.regs .rsp (by simp [calleeSaved]), args.keeps.mem] at frame
+ exact frame
+
+end VG.Proof.Argon2.X86_64.FinalOutput
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputArgs.lean
new file mode 100644
index 000000000..c9555a98e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputArgs.lean
@@ -0,0 +1,47 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FinalOutput
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionLoopState
+
+/-! Load the public final-call pointers and tag length from the enclosing frame. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalOutput
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def output (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 256) 64
+
+def work (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 248) 64
+
+def changed : List Reg := [.rdi, .rsi, .rdx, .rcx, .r8]
+
+structure Arguments (s t : State) : Prop where
+ input : t.gpr .rdi = ReductionState.matrix s
+ inputLength : t.gpr .rsi = 1024
+ output : t.gpr .rdx = output s
+ outputLength : t.gpr .rcx = s.mem.readW (off (s.gpr .rbp) 264) 64
+ work : t.gpr .r8 = work s
+ keeps : Divide.Keeps changed s t
+
+theorem args_ok (s : State) (read : ∀ d ∈ [232, 256, 264, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8) :
+ WP isa (.block Impl.Argon2.X86_64.FinalOutput.args) s (Arguments s) := by
+ have input := read 232 (by simp)
+ have out := read 256 (by simp)
+ have len := read 264 (by simp)
+ have scratch := read 248 (by simp)
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.FinalOutput.args, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ State.load64, ea_at, RegUpd.gpr_setReg, RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg,
+ input, out, len, scratch, reduceCtorEq, ite_true, ite_false, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨rfl, rfl, rfl, rfl, rfl, ?_⟩
+ constructor
+ · intro r hr
+ simp only [changed, List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2.1, hr.2.2.1, hr.2.2.2.1, hr.2.2.2.2, ite_false]
+ all_goals rfl
+
+theorem Arguments.regs {s t : State} (h : Arguments s t) (r : Reg) (hr : r ∈ calleeSaved) : t.gpr r = s.gpr r := by
+ have unchanged : r ∉ changed := by
+ 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 h.keeps.regs r unchanged
+
+end VG.Proof.Argon2.X86_64.FinalOutput
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputCT.lean
new file mode 100644
index 000000000..71156dbe6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputCT.lean
@@ -0,0 +1,56 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutputReady
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalCallCT
+
+/-! Final hashing exposes only the public tag length and pointers, for any hash backend. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalOutput
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Related (p : Params) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : ReductionState.matrix s = ReductionState.matrix t
+ outputs : output s = output t
+ works : work s = work t
+
+structure NextRelated (p : Params) (s t : State) : Prop where
+ left : FinalCall.CallReady p.tagLen s
+ right : FinalCall.CallReady p.tagLen t
+ leftInputLength : s.gpr .rsi = 1024
+ rightInputLength : t.gpr .rsi = 1024
+ leftOutputLength : s.gpr .rcx = BitVec.ofNat 64 p.tagLen
+ rightOutputLength : t.gpr .rcx = BitVec.ofNat 64 p.tagLen
+ inputs : s.gpr .rdi = t.gpr .rdi
+ outputs : s.gpr .rdx = t.gpr .rdx
+ works : s.gpr .r8 = t.gpr .r8
+ stacks : s.gpr .rsp = t.gpr .rsp
+
+theorem args_trace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block Impl.Argon2.X86_64.FinalOutput.args) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem args_rel (p : Params) : RelCT isa (Related p)
+ (.block Impl.Argon2.X86_64.FinalOutput.args) (NextRelated p) := by
+ have trace := args_trace.mono (P' := Related p) (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨args_ok s h.left.reads, args_ok t h.right.reads⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact ⟨ha.ready hp.left, hb.ready hp.right, ha.inputLength, hb.inputLength,
+ ha.outputLength.trans hp.left.tagWord, hb.outputLength.trans hp.right.tagWord,
+ ha.input.trans (hp.matrices.trans hb.input.symm), ha.output.trans (hp.outputs.trans hb.output.symm),
+ ha.work.trans (hp.works.trans hb.work.symm),
+ (ha.regs .rsp (by simp [calleeSaved])).trans (hp.stacks.trans (hb.regs .rsp (by simp [calleeSaved])).symm)⟩
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params) :
+ RelCT isa (Related p) (Impl.Argon2.X86_64.FinalOutput.code name (HPrime.hash v)) (fun _ _ => True) :=
+ (args_rel p).seq (FinalCall.hPrime_call_rel v name p.tagLen (fun _ _ h =>
+ ⟨h.left, h.right, h.leftInputLength, h.rightInputLength, h.leftOutputLength, h.rightOutputLength,
+ h.inputs, h.outputs, h.works, h.stacks⟩))
+
+end VG.Proof.Argon2.X86_64.FinalOutput
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputReady.lean
new file mode 100644
index 000000000..caaa62530
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalOutputReady.lean
@@ -0,0 +1,39 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutputArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalCall
+
+/-! Final output uses matrix block zero and the original disjoint hash scratch allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalOutput
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (s : State) : Prop where
+ positive : 1 ≤ p.tagLen
+ bound : p.tagLen < 2 ^ 32
+ reads : ∀ d ∈ [232, 256, 264, 248], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ tagWord : s.mem.readW (off (s.gpr .rbp) 264) 64 = BitVec.ofNat 64 p.tagLen
+ input : Covers [⟨ReductionState.matrix s, 1024⟩] (s.rd ++ s.wr)
+ outputWrite : Covers [⟨output s, p.tagLen⟩] s.wr
+ workWrite : (⟨work s, 16384⟩ : Region) ∈ s.wr
+ inputWork : (⟨ReductionState.matrix s, 1024⟩ : Region).Disjoint ⟨work s, 16384⟩
+ outputWork : (⟨output s, p.tagLen⟩ : Region).Disjoint ⟨work s, 16384⟩
+ stackInput : (below (s.gpr .rsp) 24).Disjoint ⟨ReductionState.matrix s, 1024⟩
+ stackOutput : (below (s.gpr .rsp) 24).Disjoint ⟨output s, p.tagLen⟩
+ stackWork : (below (s.gpr .rsp) 24).Disjoint ⟨work s, 16384⟩
+
+theorem Arguments.ready {p : Params} {s t : State} (h : Ready p s) (a : Arguments s t) :
+ FinalCall.CallReady p.tagLen t := by
+ have sp := a.regs .rsp (by simp [calleeSaved])
+ constructor
+ · exact h.positive
+ · exact h.bound
+ · rw [a.input, a.keeps.rd, a.keeps.wr]; exact h.input
+ · rw [a.output, a.keeps.wr]; exact h.outputWrite
+ · rw [a.work, a.keeps.wr]; exact h.workWrite
+ · rw [a.input, a.work]; exact h.inputWork
+ · rw [a.output, a.work]; exact h.outputWork
+ · rw [a.input, sp]; exact h.stackInput
+ · rw [a.output, sp]; exact h.stackOutput
+ · rw [a.work, sp]; exact h.stackWork
+
+end VG.Proof.Argon2.X86_64.FinalOutput
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalReduction.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalReduction.lean
new file mode 100644
index 000000000..4cfbcb29c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinalReduction.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FinalReduction
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionInit
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionInitCT
+
+/-! Complete final block reduction, including setup, termination and a public trace. -/
+
+namespace VG.Proof.Argon2.X86_64.FinalReduction
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+theorem code_ok (s : State) (p : Params) (h : ReductionInit.Ready p s) (memory : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks memory) :
+ WP isa Impl.Argon2.X86_64.FinalReduction.code s
+ (ReduceLanes.Finished s · p memory (Proof.Argon2.reduction p memory 0 p.lanes zeroBlock)) := by
+ unfold Impl.Argon2.X86_64.FinalReduction.code
+ refine WP.seq ((ReductionInit.code_ok s p h memory represented).mono ?_)
+ intro a prepared
+ refine (ReduceLanes.loop_ok p.lanes a p 0 prepared.ready memory zeroBlock prepared.represented
+ h.allocation.positive (Nat.zero_add _)).mono ?_
+ intro t finished
+ refine ⟨finished.represented, finished.base.trans prepared.base, finished.laneWord,
+ finished.rd.trans prepared.rd, finished.wr.trans prepared.wr, ?_, finished.mxcsr.trans prepared.mxcsr, ?_⟩
+ · have frame := finished.frame
+ rw [prepared.base] at frame
+ exact prepared.frame.trans frame
+ · intro r hr bx; exact (finished.regs r hr bx).trans (prepared.regs r hr bx)
+
+theorem code_rel (p : Params) (positive : 0 < p.lanes) (leftMemory rightMemory : Array Block) :
+ RelCT isa (ReductionInit.Related p leftMemory rightMemory) Impl.Argon2.X86_64.FinalReduction.code
+ (fun _ _ => True) :=
+ (ReductionInit.code_rel p leftMemory rightMemory).seq
+ (ReduceLanes.loop_rel p 0 p.lanes leftMemory rightMemory zeroBlock zeroBlock positive (Nat.zero_add _))
+
+end VG.Proof.Argon2.X86_64.FinalReduction
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishReady.lean
new file mode 100644
index 000000000..7b1833b58
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishReady.lean
@@ -0,0 +1,43 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalReduction
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutputReady
+
+/-! Preserve the final-call allocations and metadata through the matrix reduction. -/
+
+namespace VG.Proof.Argon2.X86_64.Finish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Ready (p : Params) (s : State) : Prop where
+ reduction : ReductionInit.Ready p s
+ output : FinalOutput.Ready p s
+
+theorem frame_word {s t : State} {p : Params} {memory : Array Block} {acc : Block}
+ (ready : ReductionInit.Ready p s) (done : ReduceLanes.Finished s t p memory acc)
+ (d : Nat) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.regs .rbp (by simp [calleeSaved]) (by decide)]
+ exact done.frame.readW (r := ⟨s.gpr .rbp, 272⟩) (Offset.contains_base _ bound (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact ready.allocation.frame.symm.sub_right (Region.sub_prefix (by
+ have nonempty := Proof.Argon2.lastIndex_bounds p ready.allocation.positive ready.allocation.minimum 0 ready.allocation.positive
+ omega))) (by decide)
+
+theorem output_ready {s t : State} {p : Params} {memory : Array Block} {acc : Block}
+ (ready : Ready p s) (done : ReduceLanes.Finished s t p memory acc) : FinalOutput.Ready p t := by
+ have bp := done.regs .rbp (by simp [calleeSaved]) (by decide)
+ have sp := done.regs .rsp (by simp [calleeSaved]) (by decide)
+ have output : FinalOutput.output t = FinalOutput.output s := frame_word ready.reduction done 256 (by decide)
+ have work : FinalOutput.work t = FinalOutput.work s := frame_word ready.reduction done 248 (by decide)
+ refine ⟨ready.output.positive, ready.output.bound, ?_,
+ (frame_word ready.reduction done 264 (by decide)).trans ready.output.tagWord, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [done.rd, done.wr, bp]; exact ready.output.reads
+ · rw [done.base, done.rd, done.wr]; exact ready.output.input
+ · rw [output, done.wr]; exact ready.output.outputWrite
+ · rw [work, done.wr]; exact ready.output.workWrite
+ · rw [done.base, work]; exact ready.output.inputWork
+ · rw [output, work]; exact ready.output.outputWork
+ · rw [sp, done.base]; exact ready.output.stackInput
+ · rw [sp, output]; exact ready.output.stackOutput
+ · rw [sp, work]; exact ready.output.stackWork
+
+end VG.Proof.Argon2.X86_64.Finish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStage.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStage.lean
new file mode 100644
index 000000000..aa9e52815
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStage.lean
@@ -0,0 +1,51 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Finish
+import VerifiedGarbage.Proof.Argon2.X86_64.FinishReady
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutput
+
+/-! The complete reviewed finish computation, with its enclosing frame and ABI obligations. -/
+
+namespace VG.Proof.Argon2.X86_64.Finish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+open VG.Spec.Blake2 (bytesAt)
+
+def writes (s : State) (p : Params) : List Region :=
+ [⟨matrix s, 1024⟩, ⟨FinalOutput.output s, p.tagLen⟩,
+ ⟨FinalOutput.work s, 16384⟩, below (s.gpr .rsp) 24]
+
+structure Done (s t : State) (p : Params) (memory : Array Block) : Prop where
+ digest : bytesAt t.mem (FinalOutput.output s) p.tagLen = Spec.Argon2.finish p memory
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+
+theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (p : Params)
+ (h : Ready p s) (memory : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks memory) :
+ WP isa (Impl.Argon2.X86_64.Finish.code name (HPrime.hash v)) s (Done s · p memory) := by
+ unfold Impl.Argon2.X86_64.Finish.code
+ refine WP.seq ((FinalReduction.code_ok s p h.reduction memory represented).mono ?_)
+ intro a reduced
+ refine (FinalOutput.code_ok v name a p (output_ready h reduced) memory reduced.represented.accumulator).mono ?_
+ intro t written
+ have output : FinalOutput.output a = FinalOutput.output s := frame_word h.reduction reduced 256 (by decide)
+ have work : FinalOutput.work a = FinalOutput.work s := frame_word h.reduction reduced 248 (by decide)
+ refine ⟨?_, fun r hr bx => (written.regs r hr).trans (reduced.regs r hr bx),
+ written.rd.trans reduced.rd, written.wr.trans reduced.wr, ?_⟩
+ · have digest := written.digest
+ rw [output] at digest; exact digest
+ · have firstFrame : Frame (writes s p) s.mem a.mem := reduced.frame.sub (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨_, by simp [writes], fun _ h => h⟩)
+ have lastFrame := written.frame
+ rw [output, work, reduced.regs .rsp (by simp [calleeSaved]) (by decide)] at lastFrame
+ apply firstFrame.trans
+ apply lastFrame.sub
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl <;> exact ⟨_, by simp [writes], fun _ h => h⟩
+
+end VG.Proof.Argon2.X86_64.Finish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStageCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStageCT.lean
new file mode 100644
index 000000000..b2874c355
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FinishStageCT.lean
@@ -0,0 +1,53 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FinishReady
+import VerifiedGarbage.Impl.Argon2.X86_64.Finish
+import VerifiedGarbage.Proof.Argon2.X86_64.FinalOutputCT
+
+/-! Complete finalization has a public trace for every BLAKE2b backend. -/
+
+namespace VG.Proof.Argon2.X86_64.Finish
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Related (p : Params) (leftMemory rightMemory : Array Block) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : matrix s = matrix t
+ outputs : FinalOutput.output s = FinalOutput.output t
+ works : FinalOutput.work s = FinalOutput.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (matrix s) p.blocks leftMemory
+ rightMatrix : Proof.Argon2.Represents t.mem (matrix t) p.blocks rightMemory
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params)
+ (leftMemory rightMemory : Array Block) :
+ RelCT isa (Related p leftMemory rightMemory) (Impl.Argon2.X86_64.Finish.code name (HPrime.hash v))
+ (fun _ _ => True) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq reduceA outputA =>
+ cases eb with
+ | seq reduceB outputB =>
+ have related : ReductionInit.Related p leftMemory rightMemory s t :=
+ ⟨hp.left.reduction, hp.right.reduction, hp.bases, hp.matrices, hp.leftMatrix, hp.rightMatrix⟩
+ obtain ⟨reduceTrace, _⟩ := FinalReduction.code_rel p hp.left.reduction.allocation.positive
+ leftMemory rightMemory _ _ _ _ _ _ related reduceA reduceB
+ obtain ⟨_, sa, runA, doneA⟩ := FinalReduction.code_ok s p hp.left.reduction leftMemory hp.leftMatrix
+ obtain ⟨_, sb, runB, doneB⟩ := FinalReduction.code_ok t p hp.right.reduction rightMemory hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det reduceA runA
+ obtain ⟨_, rfl⟩ := Exec.det reduceB runB
+ have finalRelated : FinalOutput.Related p _ _ :=
+ ⟨output_ready hp.left doneA, output_ready hp.right doneB,
+ (doneA.regs .rbp (by simp [calleeSaved]) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide)).symm),
+ (doneA.regs .rsp (by simp [calleeSaved]) (by decide)).trans
+ (hp.stacks.trans (doneB.regs .rsp (by simp [calleeSaved]) (by decide)).symm),
+ doneA.base.trans (hp.matrices.trans doneB.base.symm),
+ (frame_word hp.left.reduction doneA 256 (by decide)).trans
+ (hp.outputs.trans (frame_word hp.right.reduction doneB 256 (by decide)).symm),
+ (frame_word hp.left.reduction doneA 248 (by decide)).trans
+ (hp.works.trans (frame_word hp.right.reduction doneB 248 (by decide)).symm)⟩
+ obtain ⟨outputTrace, _⟩ := FinalOutput.code_rel v name p _ _ _ _ _ _ finalRelated outputA outputB
+ exact ⟨by rw [reduceTrace, outputTrace], trivial⟩
+
+end VG.Proof.Argon2.X86_64.Finish
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLane.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLane.lean
new file mode 100644
index 000000000..4d3c83efe
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLane.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.FirstLane
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! The first reference window stays in the current lane. -/
+
+namespace VG.Proof.Argon2.X86_64.FirstLane
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FirstLane
+
+theorem test_ok (s : State) : WP isa (.block test) s fun t =>
+ t.zf = decide (s.gpr .r9 = 0 ∧ s.gpr .r14 = 0) ∧ Divide.Keeps [.rax] s t := by
+ apply WP.of_runBlock
+ simp only [test, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.zf_setReg, RegUpd.zf_arithFlags,
+ reduceCtorEq, ite_true, Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, ?_⟩
+ · apply Bool.eq_iff_iff.mpr
+ simp only [beq_iff_eq, decide_eq_true_eq]
+ change (s.gpr .r9 ||| s.gpr .r14) = 0#64 ↔ _
+ exact BitVec.or_eq_zero_iff
+ · constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem current_ok (s : State) : WP isa (.block current) s fun t =>
+ t.gpr .r8 = s.gpr .rbx ∧ Divide.Keeps [.r8] s t := by
+ apply WP.of_runBlock
+ simp only [current, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ RegUpd.gpr_setReg, ite_true, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+ all_goals rfl
+
+theorem code_ok (s : State) : WP isa code s fun t =>
+ t.gpr .r8 = (if s.gpr .r9 = 0 ∧ s.gpr .r14 = 0 then s.gpr .rbx else s.gpr .r8) ∧
+ Divide.Keeps [.rax, .r8] s t := by
+ unfold code
+ refine WP.seq ((test_ok s).mono ?_)
+ rintro a ⟨flag, keeps⟩
+ refine WP.ite (decide (s.gpr .r9 = 0 ∧ s.gpr .r14 = 0))
+ (by simp only [eval, flag]) ?_ ?_
+ · intro h
+ have position := of_decide_eq_true h
+ refine (current_ok a).mono ?_
+ rintro t ⟨out, tail⟩
+ refine ⟨?_, (keeps.mono (by decide)).trans (tail.mono (by decide))⟩
+ simpa only [position, and_self, ite_true, keeps.regs .rbx (by decide)] using out
+ · intro h
+ have position := of_decide_eq_false h
+ apply WP.of_runBlock
+ simp only [runBlock_nil, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, keeps.mono (by decide)⟩
+ simp only [position, ite_false]
+ exact keeps.regs .r8 (by decide)
+
+end VG.Proof.Argon2.X86_64.FirstLane
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneCT.lean
new file mode 100644
index 000000000..f9094a469
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneCT.lean
@@ -0,0 +1,21 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.FirstLaneLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! The first-slice override branches only on the public position. -/
+
+namespace VG.Proof.Argon2.X86_64.FirstLane
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.FirstLane
+
+theorem code_rel : RelCT isa
+ (fun s t => s.gpr .r9 = t.gpr .r9 ∧ s.gpr .r14 = t.gpr .r14) code
+ (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.r9, .r14])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact h.1
+ · exact h.2)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.FirstLane
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneLit.lean
new file mode 100644
index 000000000..c128ec479
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/FirstLaneLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.FirstLane
+
+/-! A checked literal for the public first-slice lane override. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.FirstLane.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFill.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFill.lean
new file mode 100644
index 000000000..a0fa47b11
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFill.lean
@@ -0,0 +1,79 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.InitFill
+import VerifiedGarbage.Proof.Argon2.X86_64.InitFillFrames
+
+/-! Exact initialization, every filling pass, final reduction and H′ after the reviewed H₀. -/
+
+namespace VG.Proof.Argon2.X86_64.InitFill
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Spec.Blake2 (bytesAt)
+
+def result (p : Params) (h0 : List Byte) : List Byte :=
+ Spec.Argon2.finish p (Proof.Argon2.iterations p 0 p.passes (initMemory p h0)).memory
+
+structure Done (s t : State) (p : Params) : Prop where
+ digest : bytesAt t.mem (FinalOutput.output s) p.tagLen = result p (bytesAt s.mem (s.gpr .rbp) 64)
+ bp : t.gpr .rbp = s.gpr .rbp
+ sp : t.gpr .rsp = s.gpr .rsp
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s p) s.mem t.mem
+
+theorem writes_eq (s t : State) (p : Params) (bp : t.gpr .rbp = s.gpr .rbp) (sp : t.gpr .rsp = s.gpr .rsp)
+ (base : FillKernel.matrix t = FillKernel.matrix s) (work : FinalOutput.work t = FinalOutput.work s)
+ (output : FinalOutput.output t = FinalOutput.output s) : writes t p = writes s p := by
+ unfold writes
+ rw [bp, sp, base, work, output]
+
+theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (p : Params) (h : Ready p s) :
+ WP isa (Impl.Argon2.X86_64.InitFill.code name (HPrime.hash v)) s (Done s · p) := by
+ have params := h.environment.parameters
+ have q : 2 ≤ p.laneLen := by
+ have segments := Proof.Argon2.laneLen_segments p params.lanesPositive
+ have minimum := params.segment_bound.1
+ omega
+ have blocks := Proof.Argon2.lastIndex_bounds p params.lanesPositive params.segment_bound.1 0 params.lanesPositive
+ unfold Impl.Argon2.X86_64.InitFill.code
+ refine WP.seq ((MemoryInit.complete_ok v name s (FillKernel.matrix s) p.lanes p.laneLen h.initializing
+ params.lanesPositive (Nat.lt_trans params.lanesBound (by decide)) q).mono ?_)
+ intro a initialized
+ have setupReady := initialized_setup h initialized
+ have initializedBase : FillKernel.matrix a = FillKernel.matrix s :=
+ initialized.frame_word h.initializing.space 232 (by decide) (Or.inr (by decide))
+ have initializedWork : FinalOutput.work a = FinalOutput.work s :=
+ initialized.frame_word h.initializing.space 248 (by decide) (Or.inr (by decide))
+ have initializedOutput : FinalOutput.output a = FinalOutput.output s :=
+ initialized.frame_word h.initializing.space 256 (by decide) (Or.inr (by decide))
+ have rep : Proof.Argon2.Represents a.mem (FillKernel.matrix a) p.blocks
+ (initMemory p (bytesAt s.mem (s.gpr .rbp) 64)).memory := by
+ rw [initializedBase]
+ exact initialized.initialized.represents params.lanesPositive (by omega)
+ refine WP.seq ((FillSetup.code_ok a p setupReady).mono ?_)
+ intro b prepared
+ refine (FillFinish.code_ok v name b p (prepared.finish_ready setupReady (initialized_output h initialized) h.positive)
+ (initMemory p (bytesAt s.mem (s.gpr .rbp) 64)) (prepared.represents setupReady _ rep)).mono ?_
+ intro t filled
+ have bp := prepared.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)
+ have sp := prepared.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)
+ have work : FinalOutput.work b = FinalOutput.work a := prepared.words 248 (by decide) (by decide)
+ have output : FinalOutput.output b = FinalOutput.output a := prepared.words 256 (by decide) (by decide)
+ refine ⟨?_, (filled.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans (bp.trans initialized.bp),
+ (filled.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide)).trans (sp.trans initialized.sp),
+ filled.rd.trans (prepared.rd.trans initialized.rd), filled.wr.trans (prepared.wr.trans initialized.wr), ?_⟩
+ · have digest := filled.digest
+ rw [output, initializedOutput] at digest
+ exact digest
+ · have initialFrame := initialization_frame h initialized
+ have setupFrame := setup_frame (p := p) prepared.frame
+ rw [writes_eq s a p initialized.bp initialized.sp initializedBase initializedWork initializedOutput] at setupFrame
+ have fillFrame := filling_frame (by omega : 0 < p.blocks) filled.frame
+ rw [writes_eq a b p bp sp prepared.matrix work output,
+ writes_eq s a p initialized.bp initialized.sp initializedBase initializedWork initializedOutput] at fillFrame
+ exact (initialFrame.trans setupFrame).trans fillFrame
+
+theorem result_derive (p : Params) (password salt secret ad : List Byte) :
+ result p (initialHash p password salt secret ad) = derive p password salt secret ad := by
+ unfold result derive
+ rw [Proof.Argon2.iterations_fill]
+
+end VG.Proof.Argon2.X86_64.InitFill
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillCT.lean
new file mode 100644
index 000000000..0e13dc05c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillCT.lean
@@ -0,0 +1,90 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitFill
+import VerifiedGarbage.Proof.Argon2.X86_64.FillFinishCT
+
+/-! The entire post-H₀ pipeline leaks only the reviewed complete filling reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.InitFill
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Spec.Blake2 (bytesAt)
+
+def initial (p : Params) (s : State) : FillState := initMemory p (bytesAt s.mem (s.gpr .rbp) 64)
+
+structure Related (p : Params) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ outputs : FinalOutput.output s = FinalOutput.output t
+ works : FinalOutput.work s = FinalOutput.work t
+ indices : (Proof.Argon2.iterations p 0 p.passes (initial p s)).indices =
+ (Proof.Argon2.iterations p 0 p.passes (initial p t)).indices
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params) :
+ RelCT isa (Related p) (Impl.Argon2.X86_64.InitFill.code name (HPrime.hash v)) (fun _ _ => True) := by
+ intro s t ts tt a b hp ea eb
+ have params := hp.left.environment.parameters
+ have q : 2 ≤ p.laneLen := by
+ have segments := Proof.Argon2.laneLen_segments p params.lanesPositive
+ have minimum := params.segment_bound.1
+ omega
+ have lanesBound : p.lanes < 2 ^ 64 := Nat.lt_trans params.lanesBound (by decide)
+ have pub : MemoryInit.AgreeBases s t := by
+ intro r hr
+ simp only [MemoryInit.publicBases, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact hp.bases
+ · exact hp.left.scratch.trans (hp.works.trans hp.right.scratch.symm)
+ · exact hp.stacks
+ · exact hp.left.initializing.laneLength.trans hp.right.initializing.laneLength.symm
+ have rightReady : MemoryInit.Ready (FillKernel.matrix s) p.lanes p.laneLen t := by
+ rw [hp.matrices]; exact hp.right.initializing
+ cases ea with
+ | seq initA restA =>
+ cases eb with
+ | seq initB restB =>
+ have initTrace := MemoryInit.code_ct v name (FillKernel.matrix s) p.lanes p.laneLen
+ params.lanesPositive lanesBound q _ _ _ _ _ _ hp.left.initializing rightReady pub initA initB
+ obtain ⟨_, sa, runA, doneA⟩ := MemoryInit.complete_ok v name s (FillKernel.matrix s) p.lanes p.laneLen
+ hp.left.initializing params.lanesPositive lanesBound q
+ obtain ⟨_, sb, runB, doneB⟩ := MemoryInit.complete_ok v name t (FillKernel.matrix t) p.lanes p.laneLen
+ hp.right.initializing params.lanesPositive lanesBound q
+ obtain ⟨_, rfl⟩ := Exec.det initA runA
+ obtain ⟨_, rfl⟩ := Exec.det initB runB
+ cases restA with
+ | seq setupA fillA =>
+ cases restB with
+ | seq setupB fillB =>
+ have bases := doneA.bp.trans (hp.bases.trans doneB.bp.symm)
+ obtain ⟨setupTrace, _⟩ := FillSetup.code_rel _ _ _ _ _ _ bases setupA setupB
+ obtain ⟨_, ca, runA, preparedA⟩ := FillSetup.code_ok _ p (initialized_setup hp.left doneA)
+ obtain ⟨_, cb, runB, preparedB⟩ := FillSetup.code_ok _ p (initialized_setup hp.right doneB)
+ obtain ⟨_, rfl⟩ := Exec.det setupA runA
+ obtain ⟨_, rfl⟩ := Exec.det setupB runB
+ have preparedBases := (preparedA.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)).trans
+ (bases.trans (preparedB.regs .rbp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)).symm)
+ have preparedStacks := (preparedA.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)).trans
+ ((doneA.sp.trans (hp.stacks.trans doneB.sp.symm)).trans
+ (preparedB.regs .rsp (by simp [calleeSaved]) (by decide) (by decide) (by decide) (by decide)).symm)
+ have initMatrices := (doneA.frame_word hp.left.initializing.space 232 (by decide) (Or.inr (by decide))).trans
+ (hp.matrices.trans (doneB.frame_word hp.right.initializing.space 232 (by decide) (Or.inr (by decide))).symm)
+ have matrices := preparedA.matrix.trans (initMatrices.trans preparedB.matrix.symm)
+ have initOutputs := (doneA.frame_word hp.left.initializing.space 256 (by decide) (Or.inr (by decide))).trans
+ (hp.outputs.trans (doneB.frame_word hp.right.initializing.space 256 (by decide) (Or.inr (by decide))).symm)
+ have outputs := (preparedA.words 256 (by decide) (by decide)).trans
+ (initOutputs.trans (preparedB.words 256 (by decide) (by decide)).symm)
+ have initWorks := (doneA.frame_word hp.left.initializing.space 248 (by decide) (Or.inr (by decide))).trans
+ (hp.works.trans (doneB.frame_word hp.right.initializing.space 248 (by decide) (Or.inr (by decide))).symm)
+ have works := (preparedA.words 248 (by decide) (by decide)).trans
+ (initWorks.trans (preparedB.words 248 (by decide) (by decide)).symm)
+ have related : FillFinish.Related p (initial p s) (initial p t) _ _ :=
+ ⟨preparedA.finish_ready (initialized_setup hp.left doneA) (initialized_output hp.left doneA) hp.left.positive,
+ preparedB.finish_ready (initialized_setup hp.right doneB) (initialized_output hp.right doneB) hp.right.positive,
+ preparedBases, preparedStacks, matrices, outputs, works,
+ preparedA.represents (initialized_setup hp.left doneA) _ (initialized_represents hp.left doneA),
+ preparedB.represents (initialized_setup hp.right doneB) _ (initialized_represents hp.right doneB), hp.indices⟩
+ obtain ⟨fillTrace, _⟩ := FillFinish.code_rel v name p (initial p s) (initial p t) _ _ _ _ _ _ related fillA fillB
+ exact ⟨by rw [initTrace, setupTrace, fillTrace], trivial⟩
+
+end VG.Proof.Argon2.X86_64.InitFill
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillFrames.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillFrames.lean
new file mode 100644
index 000000000..4c03ead9f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillFrames.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitFillReady
+
+/-! Each stage writes only the matrix, hash scratch, output, call stack and local hash prefix. -/
+
+namespace VG.Proof.Argon2.X86_64.InitFill
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def writes (s : State) (p : Params) : List Region :=
+ [⟨FillKernel.matrix s, p.blocks * 1024⟩, ⟨FinalOutput.work s, 16384⟩,
+ ⟨FinalOutput.output s, p.tagLen⟩, below (s.gpr .rsp) 24, ⟨s.gpr .rbp, 72⟩]
+
+theorem initialization_frame {s t : State} {p : Params} (h : Ready p s)
+ (done : MemoryInit.Done s t (FillKernel.matrix s) p.lanes p.laneLen) : Frame (writes s p) s.mem t.mem := by
+ apply done.frame.sub
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · have blocks := Proof.Argon2.blocks_lanes p h.environment.parameters.lanesPositive
+ exact ⟨⟨FillKernel.matrix s, p.blocks * 1024⟩, by simp [writes], by rw [blocks, Nat.mul_comm 1024]; intro _ h; exact h⟩
+ · exact ⟨⟨FinalOutput.work s, 16384⟩, by simp [writes], by rw [h.scratch]; intro _ h; exact h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨⟨s.gpr .rbp, 72⟩, by simp [writes], Offset.sub_base _ (by decide)⟩
+
+theorem setup_frame {s t : State} {p : Params} (h : Frame [⟨s.gpr .rbp, 8⟩] s.mem t.mem) :
+ Frame (writes s p) s.mem t.mem := by
+ apply h.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨s.gpr .rbp, 72⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+
+theorem filling_frame {s t : State} {p : Params} (positive : 0 < p.blocks) (h : Frame (FillFinish.writes s p) s.mem t.mem) :
+ Frame (writes s p) s.mem t.mem := by
+ apply h.sub
+ intro r hr
+ simp only [FillFinish.writes, FillIterations.writes, Finish.writes,
+ List.mem_append, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with (rfl | rfl | rfl | rfl) | (rfl | rfl | rfl | rfl)
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨⟨FinalOutput.work s, 16384⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+ · exact ⟨below (s.gpr .rsp) 24, by simp [writes], below_sub (by decide) (by decide)⟩
+ · exact ⟨⟨s.gpr .rbp, 72⟩, by simp [writes], Region.sub_prefix (by decide)⟩
+ · exact ⟨⟨FillKernel.matrix s, p.blocks * 1024⟩, by simp [writes], Region.sub_prefix (by omega)⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+ · exact ⟨_, by simp [writes], fun _ h => h⟩
+
+end VG.Proof.Argon2.X86_64.InitFill
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillReady.lean
new file mode 100644
index 000000000..52f6fada7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitFillReady.lean
@@ -0,0 +1,77 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitDone
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSetupFinish
+
+/-! Retain the filling environment and final-call layout across memory initialization. -/
+
+namespace VG.Proof.Argon2.X86_64.InitFill
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (s : State) : Prop where
+ initializing : MemoryInit.Ready (FillKernel.matrix s) p.lanes p.laneLen s
+ environment : FillSetup.Environment p s
+ output : FinalOutput.Ready p s
+ positive : 0 < p.passes
+ scratch : s.gpr .rbx = FinalOutput.work s
+
+theorem initialized_environment {s t : State} {p : Params} (h : Ready p s)
+ (done : MemoryInit.Done s t (FillKernel.matrix s) p.lanes p.laneLen) : FillSetup.Environment p t := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word h.initializing.space 232 (by decide) (Or.inr (by decide))
+ have work : AddressCalls.work t = AddressCalls.work s := done.frame_word h.initializing.space 248 (by decide) (Or.inr (by decide))
+ have e := h.environment
+ refine ⟨e.parameters, e.passesBound, e.layout.of_preserved done.bp done.sp base work done.rd done.wr,
+ ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · constructor
+ · rw [done.rd, done.wr, done.bp]; exact e.addressLayout.frameRead
+ · rw [done.wr, work]; exact e.addressLayout.workWrite
+ · rw [done.bp, work]; exact e.addressLayout.frameWork
+ · rw [done.bp, done.sp]; exact e.addressLayout.frameStack
+ · rw [done.sp, work]; exact e.addressLayout.stackWork
+ · rw [done.rd, done.wr, done.bp]; exact e.reads
+ · rw [done.wr, done.bp]; exact e.counterWrite
+ · rw [done.wr, done.bp]; exact e.passWrite
+ · rw [base, work]; exact e.matrixWork
+ · exact (done.frame_word h.initializing.space 240 (by decide) (Or.inr (by decide))).trans e.blocksWord
+ · exact (done.frame_word h.initializing.space 72 (by decide) (Or.inr (by decide))).trans e.passesWord
+ · exact (done.frame_word h.initializing.space 112 (by decide) (Or.inr (by decide))).trans e.variantWord
+ · exact (done.frame_word h.initializing.space 184 (by decide) (Or.inr (by decide))).trans e.lanesWord
+
+theorem initialized_output {s t : State} {p : Params} (h : Ready p s)
+ (done : MemoryInit.Done s t (FillKernel.matrix s) p.lanes p.laneLen) : FinalOutput.Ready p t := by
+ have base : ReductionState.matrix t = ReductionState.matrix s := done.frame_word h.initializing.space 232 (by decide) (Or.inr (by decide))
+ have output : FinalOutput.output t = FinalOutput.output s := done.frame_word h.initializing.space 256 (by decide) (Or.inr (by decide))
+ have work : FinalOutput.work t = FinalOutput.work s := done.frame_word h.initializing.space 248 (by decide) (Or.inr (by decide))
+ refine ⟨h.output.positive, h.output.bound, ?_,
+ (done.frame_word h.initializing.space 264 (by decide) (Or.inr (by decide))).trans h.output.tagWord,
+ ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [done.rd, done.wr, done.bp]; exact h.output.reads
+ · rw [base, done.rd, done.wr]; exact h.output.input
+ · rw [output, done.wr]; exact h.output.outputWrite
+ · rw [work, done.wr]; exact h.output.workWrite
+ · rw [base, work]; exact h.output.inputWork
+ · rw [output, work]; exact h.output.outputWork
+ · rw [done.sp, base]; exact h.output.stackInput
+ · rw [done.sp, output]; exact h.output.stackOutput
+ · rw [done.sp, work]; exact h.output.stackWork
+
+theorem initialized_setup {s t : State} {p : Params} (h : Ready p s)
+ (done : MemoryInit.Done s t (FillKernel.matrix s) p.lanes p.laneLen) : FillSetup.Ready p t := by
+ have params := h.environment.parameters
+ have product : p.laneLen ≤ p.lanes * p.laneLen := by
+ simpa only [Nat.one_mul] using Nat.mul_le_mul_right p.laneLen (show 1 ≤ p.lanes from params.lanesPositive)
+ refine ⟨initialized_environment h done, ?_, done.stride⟩
+ have bound := h.initializing.space.bound
+ have bytes := Nat.mul_le_mul_left 1024 product
+ omega
+
+theorem initialized_represents {s t : State} {p : Params} (h : Ready p s)
+ (done : MemoryInit.Done s t (FillKernel.matrix s) p.lanes p.laneLen) :
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks
+ (initMemory p (Spec.Blake2.bytesAt s.mem (s.gpr .rbp) 64)).memory := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word h.initializing.space 232 (by decide) (Or.inr (by decide))
+ rw [base]
+ have segments := Proof.Argon2.laneLen_segments p h.environment.parameters.lanesPositive
+ have minimum := h.environment.parameters.segment_bound.1
+ exact done.initialized.represents h.environment.parameters.lanesPositive (by omega)
+
+end VG.Proof.Argon2.X86_64.InitFill
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialAbsorbCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialAbsorbCT.lean
new file mode 100644
index 000000000..768132ccb
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialAbsorbCT.lean
@@ -0,0 +1,119 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialUpdateReady
+import VerifiedGarbage.Proof.Framework.RelCTAssoc
+
+/-! H₀ updates depend on public lengths and pointers, never on input contents. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.Initial
+
+def PrefixRelated (lo : Nat) (a b : State) : Prop :=
+ True ∧ ∃ s t, RelatedRegs [.r12] s t ∧ LengthArgs s lo a ∧ LengthArgs t lo b
+
+theorem prefix_rel (v : Proof.Blake2.X86_64.Backend) (lo : Nat) (slot : lo ∈ slots)
+ (bound : lo + 8 ≤ 272)
+ (check : ∃ hint, (taint.check (Taint.ofRegs [.rbp, .rbx]) (.block (lengthArgs lo)) hint).isSome = true) :
+ RelCT isa (RelatedRegs [.r12])
+ (.seq (.block (lengthArgs lo)) (Impl.Argon2.X86_64.HPrime.update (HPrime.hash v))) (LengthRelated lo) := by
+ have args := ((lengthArgs_rel lo check).mono (P' := RelatedRegs [.r12])
+ (fun _ _ h => ⟨h.1.bp, h.1.bx⟩) (fun _ _ h => h)).wpDep
+ (fun s t h => ⟨lengthArgs_ok s lo (h.1.left.space.readable lo slot)
+ (by simpa using h.1.left.space.write 792 4 (by decide)),
+ lengthArgs_ok t lo (h.1.right.space.readable lo slot)
+ (by simpa using h.1.right.space.write 792 4 (by decide))⟩)
+ have call := HPrime.update_rel v (P := PrefixRelated lo) (fun a b ⟨_, s, t, hp, ha, hb⟩ =>
+ ⟨prefix_update_ready hp.1.left.space ha, prefix_update_ready hp.1.right.space hb,
+ by rw [ha.keeps.rbx, hb.keeps.rbx, hp.1.bx], by rw [ha.count, hb.count]; exact hp.2 _ (by simp),
+ by rw [ha.pointer, hb.pointer, hp.1.bx], by rw [ha.size, hb.size],
+ by rw [ha.keeps.rsp, hb.keeps.rsp, hp.1.sp]⟩)
+ have called := call.wpDep (fun a b ⟨_, s, t, hp, ha, hb⟩ =>
+ ⟨HPrime.update_keeps v a (prefix_update_ready hp.1.left.space ha),
+ HPrime.update_keeps v b (prefix_update_ready hp.1.right.space hb)⟩)
+ have finished := called.mono (fun _ _ h => h) (fun a b h => by
+ obtain ⟨_, x, y, ⟨_, s, t, hp, ha, hb⟩, ka, kb⟩ := h
+ have rel := hp.1.keeps ha.keeps hb.keeps
+ have prepared : LengthRelated lo x y := by
+ refine ⟨⟨rel, ?_⟩, ?_, ?_⟩
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · rw [ha.other _ (by decide) (by decide) (by decide) (by decide),
+ hb.other _ (by decide) (by decide) (by decide) (by decide)]
+ exact hp.2 _ (by simp)
+ · rw [ha.length, hb.length]; exact hp.1.words lo slot
+ · rw [hp.1.left.space.word_keeps ha.keeps lo bound]; exact ha.length
+ · rw [hp.1.right.space.word_keeps hb.keeps lo bound]; exact hb.length
+ exact prepared.hash_keeps bound ka kb)
+ exact args.seq finished
+
+def InputRelated (lo po : Nat) (a b : State) : Prop :=
+ True ∧ ∃ s t, LengthRelated lo s t ∧ InputArgs s a po ∧ InputArgs t b po
+
+theorem input_rel (v : Proof.Blake2.X86_64.Backend) (po lo : Nat) (input : (po, lo) ∈ inputs)
+ (check : ∃ hint, (taint.check (Taint.ofRegs [.rbp]) (.block (inputArgs po)) hint).isSome = true) :
+ RelCT isa (LengthRelated lo)
+ (.seq (.block (inputArgs po)) (Impl.Argon2.X86_64.HPrime.update (HPrime.hash v))) (LengthRelated lo) := by
+ have args := ((inputArgs_rel po check).mono (P' := LengthRelated lo)
+ (fun _ _ h => h.related.1.bp) (fun _ _ h => h)).wpDep
+ (fun s t h => ⟨inputArgs_ok s po (h.related.1.left.space.readable po (h.related.1.left.inputs _ input).pointerSlot),
+ inputArgs_ok t po (h.related.1.right.space.readable po (h.related.1.right.inputs _ input).pointerSlot)⟩)
+ have call := HPrime.update_rel v (P := InputRelated lo po) (fun a b ⟨_, s, t, hp, ha, hb⟩ =>
+ ⟨input_update_ready (hp.related.1.left.inputs _ input) hp.leftLength ha,
+ input_update_ready (hp.related.1.right.inputs _ input) hp.rightLength hb,
+ by rw [ha.keeps.rbx, hb.keeps.rbx, hp.related.1.bx],
+ by rw [ha.count, hb.count, hp.related.2 .r12 (by simp)],
+ by rw [ha.pointer, hb.pointer]; exact hp.related.1.words po (hp.related.1.left.inputs _ input).pointerSlot,
+ by rw [ha.length, hb.length]; exact hp.related.2 .r14 (by simp),
+ by rw [ha.keeps.rsp, hb.keeps.rsp, hp.related.1.sp]⟩)
+ have called := call.wpDep (fun a b ⟨_, s, t, hp, ha, hb⟩ =>
+ ⟨HPrime.update_keeps v a (input_update_ready (hp.related.1.left.inputs _ input) hp.leftLength ha),
+ HPrime.update_keeps v b (input_update_ready (hp.related.1.right.inputs _ input) hp.rightLength hb)⟩)
+ have finished := called.mono (fun _ _ h => h) (fun a b h => by
+ obtain ⟨_, x, y, ⟨_, s, t, hp, ha, hb⟩, ka, kb⟩ := h
+ have left := hp.related.1.left.inputs _ input
+ have right := hp.related.1.right.inputs _ input
+ have prepared : LengthRelated lo x y := by
+ refine ⟨⟨hp.related.1.keeps ha.keeps hb.keeps, ?_⟩, ?_, ?_⟩
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · rw [ha.total, hb.total, hp.related.2 .r12 (by simp)]
+ · rw [ha.other _ (by decide) (by decide) (by decide) (by decide),
+ hb.other _ (by decide) (by decide) (by decide) (by decide)]
+ exact hp.related.2 .r14 (by simp)
+ · rw [left.space.word_keeps ha.keeps lo left.lengthBound,
+ ha.other _ (by decide) (by decide) (by decide) (by decide)]
+ exact hp.leftLength
+ · rw [right.space.word_keeps hb.keeps lo right.lengthBound,
+ hb.other _ (by decide) (by decide) (by decide) (by decide)]
+ exact hp.rightLength
+ exact prepared.hash_keeps left.lengthBound ka kb)
+ exact args.seq finished
+
+theorem addCount_rel (lo : Nat) :
+ RelCT isa (LengthRelated lo) (.block [.alu .add .r12 (.reg .r14)]) (RelatedRegs [.r12]) := by
+ have ct := (RelCT.taint (A := taint) (P := LengthRelated lo) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block [.alu .add .r12 (.reg .r14)]) (by taint_decide)).wpDep
+ (fun s t _ => ⟨addCount_ok s, addCount_ok t⟩)
+ apply ct.mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ⟨ca, _, ka⟩, ⟨cb, _, kb⟩⟩
+ refine ⟨hp.related.1.keeps ka kb, ?_⟩
+ intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ rw [ca, cb, hp.related.2 .r12 (by simp), hp.related.2 .r14 (by simp)]
+
+theorem absorb_rel (v : Proof.Blake2.X86_64.Backend) (po lo : Nat) (input : (po, lo) ∈ inputs)
+ (lengthCheck : ∃ hint, (taint.check (Taint.ofRegs [.rbp, .rbx]) (.block (lengthArgs lo)) hint).isSome = true)
+ (pointerCheck : ∃ hint, (taint.check (Taint.ofRegs [.rbp]) (.block (inputArgs po)) hint).isSome = true) :
+ RelCT isa (RelatedRegs [.r12]) (absorb (HPrime.hash v) po lo) (RelatedRegs [.r12]) := by
+ have slot : lo ∈ slots := by
+ have all : ∀ p ∈ inputs, p.2 ∈ slots := by decide
+ exact all _ input
+ have bound : lo + 8 ≤ 272 := by
+ have all : ∀ d ∈ slots, d + 8 ≤ 272 := by decide
+ exact all lo slot
+ exact ((prefix_rel v lo slot bound lengthCheck).seq
+ (((input_rel v po lo input pointerCheck).seq (addCount_rel lo)).assoc)).assoc
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBody.lean
new file mode 100644
index 000000000..e015a6214
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBody.lean
@@ -0,0 +1,60 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.InitialBody
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyReady
+
+/-! H₀, initialization, every filling pass, and finalization agree with derive. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Impl.Argon2.X86_64.Initial
+open VG.Spec.Blake2 (bytesAt)
+
+structure Ready (p : Params) (s : State) : Prop where
+ hashSpace : Initial.Space s
+ inputs : ∀ input ∈ Initial.inputs, Initial.InputReady s input.1 input.2
+ header : Initial.headerBytes s = Proof.Argon2.initialHeader p
+ filling : InitFill.Ready p s
+
+structure Done (s t : State) (p : Params) : Prop where
+ digest : bytesAt t.mem (FinalOutput.output s) p.tagLen = derive p
+ (Initial.inputBytes s passwordOffset passwordLenOffset)
+ (Initial.inputBytes s saltOffset saltLenOffset)
+ (Initial.inputBytes s secretOffset secretLenOffset)
+ (Initial.inputBytes s adOffset adLenOffset)
+ bp : t.gpr .rbp = s.gpr .rbp
+ sp : t.gpr .rsp = s.gpr .rsp
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (InitFill.writes s p) s.mem t.mem
+
+theorem hash_frame {s t : State} {p : Params} (h : InitFill.Ready p s) (done : Initial.Finished s t) :
+ Frame (InitFill.writes s p) s.mem t.mem := by
+ apply done.frame.sub
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact ⟨⟨FinalOutput.work s, 16384⟩, by simp [InitFill.writes], by
+ rw [h.scratch]; exact Region.sub_prefix (by decide)⟩
+ · exact ⟨below (s.gpr .rsp) 24, by simp [InitFill.writes], below_sub (by decide) (by decide)⟩
+ · exact ⟨⟨s.gpr .rbp, 72⟩, by simp [InitFill.writes], Region.sub_prefix (by decide)⟩
+
+theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State) (p : Params) (h : Ready p s) :
+ WP isa (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v)) s (Done s · p) := by
+ unfold Impl.Argon2.X86_64.InitialBody.code
+ refine WP.seq ((Initial.initialHash_ok v s h.hashSpace h.inputs p h.header).mono ?_)
+ rintro a ⟨digest, hashed⟩
+ refine (InitFill.code_ok v name a p (hashed_ready h.filling h.hashSpace hashed)).mono ?_
+ intro t filled
+ have base : FillKernel.matrix a = FillKernel.matrix s := hashed.frame_word h.hashSpace 232 (by decide) (by decide)
+ have work : FinalOutput.work a = FinalOutput.work s := hashed.frame_word h.hashSpace 248 (by decide) (by decide)
+ have output : FinalOutput.output a = FinalOutput.output s := hashed.frame_word h.hashSpace 256 (by decide) (by decide)
+ refine ⟨?_, filled.bp.trans hashed.rbp, filled.sp.trans hashed.rsp,
+ filled.rd.trans hashed.rd, filled.wr.trans hashed.wr, ?_⟩
+ · have result := filled.digest
+ rw [output, hashed.rbp, digest, InitFill.result_derive] at result
+ exact result
+ · have frame := filled.frame
+ rw [InitFill.writes_eq s a p hashed.rbp hashed.rsp base work output] at frame
+ exact (hash_frame h.filling hashed).trans frame
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyCT.lean
new file mode 100644
index 000000000..fe4419256
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyCT.lean
@@ -0,0 +1,51 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBody
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialCT
+import VerifiedGarbage.Proof.Argon2.X86_64.InitFillCT
+
+/-! Complete derivation reveals only its reviewed filling reference sequence. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Impl.Argon2.X86_64.Initial
+open VG.Spec.Blake2 (bytesAt)
+
+def initial (p : Params) (s : State) : FillState := initMemory p (initialHash p
+ (Initial.inputBytes s passwordOffset passwordLenOffset)
+ (Initial.inputBytes s saltOffset saltLenOffset)
+ (Initial.inputBytes s secretOffset secretLenOffset)
+ (Initial.inputBytes s adOffset adLenOffset))
+
+structure Related (p : Params) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ hashing : Initial.Related s t
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ outputs : FinalOutput.output s = FinalOutput.output t
+ works : FinalOutput.work s = FinalOutput.work t
+ indices : (Proof.Argon2.iterations p 0 p.passes (initial p s)).indices =
+ (Proof.Argon2.iterations p 0 p.passes (initial p t)).indices
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params) :
+ RelCT isa (Related p) (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v)) (fun _ _ => True) := by
+ have hashed := ((Initial.code_rel v).mono (P' := Related p) (fun _ _ h => h.hashing)
+ (fun _ _ h => h)).wpDep (fun s t h =>
+ ⟨Initial.initialHash_ok v s h.left.hashSpace h.left.inputs p h.left.header,
+ Initial.initialHash_ok v t h.right.hashSpace h.right.inputs p h.right.header⟩)
+ refine hashed.seq ((InitFill.code_rel v name p).mono ?_ (fun _ _ h => h))
+ rintro a b ⟨_, s, t, hp, ⟨da, ha⟩, ⟨db, hb⟩⟩
+ have baseA : FillKernel.matrix a = FillKernel.matrix s := ha.frame_word hp.left.hashSpace 232 (by decide) (by decide)
+ have baseB : FillKernel.matrix b = FillKernel.matrix t := hb.frame_word hp.right.hashSpace 232 (by decide) (by decide)
+ have outputA : FinalOutput.output a = FinalOutput.output s := ha.frame_word hp.left.hashSpace 256 (by decide) (by decide)
+ have outputB : FinalOutput.output b = FinalOutput.output t := hb.frame_word hp.right.hashSpace 256 (by decide) (by decide)
+ have workA : FinalOutput.work a = FinalOutput.work s := ha.frame_word hp.left.hashSpace 248 (by decide) (by decide)
+ have workB : FinalOutput.work b = FinalOutput.work t := hb.frame_word hp.right.hashSpace 248 (by decide) (by decide)
+ refine ⟨hashed_ready hp.left.filling hp.left.hashSpace ha, hashed_ready hp.right.filling hp.right.hashSpace hb,
+ ha.rbp.trans (hp.hashing.bp.trans hb.rbp.symm), ha.rsp.trans (hp.hashing.sp.trans hb.rsp.symm),
+ baseA.trans (hp.matrices.trans baseB.symm), outputA.trans (hp.outputs.trans outputB.symm),
+ workA.trans (hp.works.trans workB.symm), ?_⟩
+ unfold InitFill.initial
+ rw [ha.rbp, hb.rbp, da, db]
+ exact hp.indices
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReady.lean
new file mode 100644
index 000000000..080fa023f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReady.lean
@@ -0,0 +1,65 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialMetadata
+import VerifiedGarbage.Proof.Argon2.X86_64.InitFill
+
+/-! H₀ retains the allocation and parameter environment of complete derivation. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem hashed_environment {s t : State} {p : Params} (h : InitFill.Ready p s) (space : Initial.Space s) (done : Initial.Finished s t) : FillSetup.Environment p t := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word space 232 (by decide) (by decide)
+ have work : AddressCalls.work t = AddressCalls.work s := done.frame_word space 248 (by decide) (by decide)
+ have e := h.environment
+ refine ⟨e.parameters, e.passesBound, e.layout.of_preserved done.rbp done.rsp base work done.rd done.wr,
+ ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · constructor
+ · rw [done.rd, done.wr, done.rbp]; exact e.addressLayout.frameRead
+ · rw [done.wr, work]; exact e.addressLayout.workWrite
+ · rw [done.rbp, work]; exact e.addressLayout.frameWork
+ · rw [done.rbp, done.rsp]; exact e.addressLayout.frameStack
+ · rw [done.rsp, work]; exact e.addressLayout.stackWork
+ · rw [done.rd, done.wr, done.rbp]; exact e.reads
+ · rw [done.wr, done.rbp]; exact e.counterWrite
+ · rw [done.wr, done.rbp]; exact e.passWrite
+ · rw [base, work]; exact e.matrixWork
+ · exact (done.frame_word space 240 (by decide) (by decide)).trans e.blocksWord
+ · exact (done.frame_word space 72 (by decide) (by decide)).trans e.passesWord
+ · exact (done.frame_word space 112 (by decide) (by decide)).trans e.variantWord
+ · exact (done.frame_word space 184 (by decide) (by decide)).trans e.lanesWord
+
+theorem hashed_output {s t : State} {p : Params} (h : InitFill.Ready p s) (space : Initial.Space s) (done : Initial.Finished s t) : FinalOutput.Ready p t := by
+ have base : ReductionState.matrix t = ReductionState.matrix s := done.frame_word space 232 (by decide) (by decide)
+ have output : FinalOutput.output t = FinalOutput.output s := done.frame_word space 256 (by decide) (by decide)
+ have work : FinalOutput.work t = FinalOutput.work s := done.frame_word space 248 (by decide) (by decide)
+ refine ⟨h.output.positive, h.output.bound, ?_,
+ (done.frame_word space 264 (by decide) (by decide)).trans h.output.tagWord,
+ ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [done.rd, done.wr, done.rbp]; exact h.output.reads
+ · rw [base, done.rd, done.wr]; exact h.output.input
+ · rw [output, done.wr]; exact h.output.outputWrite
+ · rw [work, done.wr]; exact h.output.workWrite
+ · rw [base, work]; exact h.output.inputWork
+ · rw [output, work]; exact h.output.outputWork
+ · rw [done.rsp, base]; exact h.output.stackInput
+ · rw [done.rsp, output]; exact h.output.stackOutput
+ · rw [done.rsp, work]; exact h.output.stackWork
+
+theorem hashed_ready {s t : State} {p : Params} (h : InitFill.Ready p s)
+ (space : Initial.Space s) (done : Initial.Finished s t) : InitFill.Ready p t := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := done.frame_word space 232 (by decide) (by decide)
+ have work : FinalOutput.work t = FinalOutput.work s := done.frame_word space 248 (by decide) (by decide)
+ refine ⟨?_, hashed_environment h space done, hashed_output h space done, h.positive, ?_⟩
+ · constructor
+ · rw [base]
+ exact h.initializing.space.same done.wr done.rbp done.rbx done.rsp
+ · rw [done.rd, done.wr, done.rbp]; exact h.initializing.memoryRead
+ · rw [done.rd, done.wr, done.rbp]; exact h.initializing.lanesRead
+ · rw [done.rd, done.wr, done.rbp]; exact h.initializing.blocksRead
+ · exact (done.frame_word space 232 (by decide) (by decide)).trans h.initializing.memoryWord |>.trans base.symm
+ · exact (done.frame_word space 184 (by decide) (by decide)).trans h.initializing.lanesWord
+ · exact (done.frame_word space 240 (by decide) (by decide)).trans h.initializing.blocksWord
+ · exact (done.regs .r13 (by decide) (by decide) (by decide)).trans h.initializing.laneLength
+ · rw [done.rbx, work]; exact h.scratch
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedCT.lean
new file mode 100644
index 000000000..ac230e336
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedCT.lean
@@ -0,0 +1,42 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyCT
+import VerifiedGarbage.Proof.Argon2.References
+
+/-! Use exactly the flattened leakage allowance of the shared derive contract. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Impl.Argon2.X86_64.Initial
+
+def references (p : Params) (s : State) : List Nat := Spec.Argon2.references p
+ (Initial.inputBytes s passwordOffset passwordLenOffset)
+ (Initial.inputBytes s saltOffset saltLenOffset)
+ (Initial.inputBytes s secretOffset secretLenOffset)
+ (Initial.inputBytes s adOffset adLenOffset)
+
+structure ReviewedRelated (p : Params) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ hashing : Initial.Related s t
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ outputs : FinalOutput.output s = FinalOutput.output t
+ works : FinalOutput.work s = FinalOutput.work t
+ references : references p s = references p t
+
+theorem ReviewedRelated.related {p : Params} {s t : State} (h : ReviewedRelated p s t) : Related p s t := by
+ refine ⟨h.left, h.right, h.hashing, h.matrices, h.outputs, h.works, ?_⟩
+ have parameters := h.left.filling.environment.parameters
+ have positive : 0 < p.laneLen := by
+ have segments := Proof.Argon2.laneLen_segments p parameters.lanesPositive
+ have minimum := parameters.segment_bound.1
+ omega
+ have indices := Proof.Argon2.references_injective p positive _ _ _ _ _ _ _ _ h.references
+ unfold initial
+ rw [Proof.Argon2.iterations_fill, Proof.Argon2.iterations_fill]
+ exact indices
+
+theorem reviewed_rel (v : Proof.Blake2.X86_64.Backend) (name : String) (p : Params) :
+ RelCT isa (ReviewedRelated p) (Impl.Argon2.X86_64.InitialBody.code name (HPrime.hash v)) (fun _ _ => True) :=
+ (code_rel v name p).mono (fun _ _ h => h.related) (fun _ _ h => h)
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedState.lean
new file mode 100644
index 000000000..d2446dc82
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyReviewedState.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyReviewedCT
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBodyState
+
+/-! Preserve precisely the reviewed leakage relation across parameter computation. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Impl.Argon2.X86_64.Initial
+
+theorem ReviewedRelated.of_state {s₁ s₂ t₁ t₂ : State} {p : Params}
+ (h : ReviewedRelated p s₁ s₂) (k₁ : SameFrame s₁ t₁) (k₂ : SameFrame s₂ t₂)
+ (length₁ : t₁.gpr .r13 = BitVec.ofNat 64 p.laneLen)
+ (length₂ : t₂.gpr .r13 = BitVec.ofNat 64 p.laneLen) : ReviewedRelated p t₁ t₂ := by
+ refine ⟨h.left.of_state k₁ length₁, h.right.of_state k₂ length₂, ?_, ?_, ?_, ?_, ?_⟩
+ · refine ⟨⟨k₁.hashSpace h.hashing.left.space,
+ fun input hi => k₁.input (h.hashing.left.inputs input hi)⟩,
+ ⟨k₂.hashSpace h.hashing.right.space,
+ fun input hi => k₂.input (h.hashing.right.inputs input hi)⟩, ?_, ?_, ?_, ?_⟩
+ · rw [k₁.bp, k₂.bp]; exact h.hashing.bp
+ · rw [k₁.bx, k₂.bx]; exact h.hashing.bx
+ · rw [k₁.sp, k₂.sp]; exact h.hashing.sp
+ · intro d hd; rw [k₁.word d, k₂.word d]; exact h.hashing.words d hd
+ · unfold FillKernel.matrix; rw [k₁.mem, k₂.mem, k₁.bp, k₂.bp]; exact h.matrices
+ · unfold FinalOutput.output; rw [k₁.mem, k₂.mem, k₁.bp, k₂.bp]; exact h.outputs
+ · unfold FinalOutput.work; rw [k₁.mem, k₂.mem, k₁.bp, k₂.bp]; exact h.works
+ · unfold VG.Proof.Argon2.X86_64.InitialBody.references
+ rw [k₁.inputBytes passwordOffset passwordLenOffset, k₂.inputBytes passwordOffset passwordLenOffset,
+ k₁.inputBytes saltOffset saltLenOffset, k₂.inputBytes saltOffset saltLenOffset,
+ k₁.inputBytes secretOffset secretLenOffset, k₂.inputBytes secretOffset secretLenOffset,
+ k₁.inputBytes adOffset adLenOffset, k₂.inputBytes adOffset adLenOffset]
+ exact h.references
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyState.lean
new file mode 100644
index 000000000..8b043079f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialBodyState.lean
@@ -0,0 +1,97 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBody
+
+/-! The complete body depends on the frame, allocation, and computed lane length. -/
+
+namespace VG.Proof.Argon2.X86_64.InitialBody
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure SameFrame (s t : State) : Prop where
+ bp : t.gpr .rbp = s.gpr .rbp
+ bx : t.gpr .rbx = s.gpr .rbx
+ sp : t.gpr .rsp = s.gpr .rsp
+ mem : t.mem = s.mem
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+
+theorem SameFrame.word {s t : State} (k : SameFrame s t) (d : Nat) :
+ Initial.wordAt t d = Initial.wordAt s d := by
+ unfold Initial.wordAt; rw [k.mem, k.bp]
+
+theorem SameFrame.inputBytes {s t : State} (k : SameFrame s t) (po lo : Nat) :
+ Initial.inputBytes t po lo = Initial.inputBytes s po lo := by
+ unfold Initial.inputBytes; rw [k.word po, k.word lo, k.mem]
+
+theorem SameFrame.hashSpace {s t : State} (k : SameFrame s t)
+ (h : Initial.Space s) : Initial.Space t := by
+ constructor
+ · rw [k.bx, k.wr]; exact h.work
+ · rw [k.sp, k.bx]; exact h.stackWork
+ · rw [k.bp, k.bx]; exact h.frameWork
+ · rw [k.bp, k.sp]; exact h.frameStack
+ · rw [k.rd, k.wr, k.bp]; exact h.readable
+ · rw [k.wr, k.bp]; exact h.output
+
+theorem SameFrame.input {s t : State} (k : SameFrame s t) {po lo : Nat}
+ (h : Initial.InputReady s po lo) : Initial.InputReady t po lo := by
+ have word : ∀ d, Initial.wordAt t d = Initial.wordAt s d := by
+ intro d; unfold Initial.wordAt; rw [k.mem, k.bp]
+ have region : Initial.inputRegion t po lo = Initial.inputRegion s po lo := by
+ unfold Initial.inputRegion; rw [word po, word lo]
+ refine ⟨k.hashSpace h.space, h.pointerSlot, h.lengthSlot, h.pointerBound,
+ h.lengthBound, ?_, ?_, ?_, ?_⟩
+ · rw [word lo]; exact h.length
+ · rw [region, k.rd, k.wr]; exact h.cover
+ · rw [region, k.bx]; exact h.work
+ · rw [region, k.sp]; exact h.stack
+
+theorem SameFrame.output {s t : State} (k : SameFrame s t) {p : Params}
+ (h : FinalOutput.Ready p s) : FinalOutput.Ready p t := by
+ have base : ReductionState.matrix t = ReductionState.matrix s := by
+ unfold ReductionState.matrix; rw [k.mem, k.bp]
+ have work : FinalOutput.work t = FinalOutput.work s := by
+ unfold FinalOutput.work; rw [k.mem, k.bp]
+ have output : FinalOutput.output t = FinalOutput.output s := by
+ unfold FinalOutput.output; rw [k.mem, k.bp]
+ refine ⟨h.positive, h.bound, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [k.rd, k.wr, k.bp]; exact h.reads
+ · rw [k.mem, k.bp]; exact h.tagWord
+ · rw [base, k.rd, k.wr]; exact h.input
+ · rw [output, k.wr]; exact h.outputWrite
+ · rw [work, k.wr]; exact h.workWrite
+ · rw [base, work]; exact h.inputWork
+ · rw [output, work]; exact h.outputWork
+ · rw [k.sp, base]; exact h.stackInput
+ · rw [k.sp, output]; exact h.stackOutput
+ · rw [k.sp, work]; exact h.stackWork
+
+theorem SameFrame.filling {s t : State} (k : SameFrame s t) {p : Params}
+ (h : InitFill.Ready p s) (laneLength : t.gpr .r13 = BitVec.ofNat 64 p.laneLen) :
+ InitFill.Ready p t := by
+ have base : FillKernel.matrix t = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [k.mem, k.bp]
+ have work : FinalOutput.work t = FinalOutput.work s := by
+ unfold FinalOutput.work; rw [k.mem, k.bp]
+ refine ⟨?_, h.environment.of_state k.bp k.sp k.mem k.rd k.wr,
+ k.output h.output, h.positive, ?_⟩
+ · constructor
+ · rw [base]; exact h.initializing.space.same k.wr k.bp k.bx k.sp
+ · rw [k.rd, k.wr, k.bp]; exact h.initializing.memoryRead
+ · rw [k.rd, k.wr, k.bp]; exact h.initializing.lanesRead
+ · rw [k.rd, k.wr, k.bp]; exact h.initializing.blocksRead
+ · unfold Initial.wordAt; rw [k.mem, k.bp, base]; exact h.initializing.memoryWord
+ · unfold Initial.wordAt; rw [k.mem, k.bp]; exact h.initializing.lanesWord
+ · unfold Initial.wordAt; rw [k.mem, k.bp]; exact h.initializing.blocksWord
+ · exact laneLength
+ · rw [k.bx, work]; exact h.scratch
+
+theorem Ready.of_state {s t : State} {p : Params} (h : Ready p s)
+ (k : SameFrame s t) (laneLength : t.gpr .r13 = BitVec.ofNat 64 p.laneLen) : Ready p t := by
+ refine ⟨k.hashSpace h.hashSpace, fun input hi => k.input (h.inputs input hi), ?_,
+ k.filling h.filling laneLength⟩
+ have header : Initial.headerBytes t = Initial.headerBytes s := by
+ unfold Initial.headerBytes
+ simp only [Initial.headerValue, Initial.wordAt, k.mem, k.bp]
+ exact header.trans h.header
+
+end VG.Proof.Argon2.X86_64.InitialBody
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCT.lean
new file mode 100644
index 000000000..c92bd5f1f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCT.lean
@@ -0,0 +1,20 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialStartCT
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialAbsorbCT
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialFinishCT
+
+/-! Complete H₀ is constant time for every verified BLAKE2b backend. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.Initial
+
+theorem code_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa Related (code (HPrime.hash v)) (fun _ _ => True) :=
+ (start_rel v).seq
+ ((absorb_rel v passwordOffset passwordLenOffset (by decide) ⟨_, by taint_decide⟩ ⟨_, by taint_decide⟩).seq
+ ((absorb_rel v saltOffset saltLenOffset (by decide) ⟨_, by taint_decide⟩ ⟨_, by taint_decide⟩).seq
+ ((absorb_rel v secretOffset secretLenOffset (by decide) ⟨_, by taint_decide⟩ ⟨_, by taint_decide⟩).seq
+ ((absorb_rel v adOffset adLenOffset (by decide) ⟨_, by taint_decide⟩ ⟨_, by taint_decide⟩).seq
+ (finish_rel v)))))
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCTState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCTState.lean
new file mode 100644
index 000000000..443c109d6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialCTState.lean
@@ -0,0 +1,57 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.Initial
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialBlocksCT
+import VerifiedGarbage.Proof.Argon2.X86_64.HPrime.FixedCT
+
+/-! Public input metadata survives every hash call without relating input bytes. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64
+
+structure Ready (s : State) : Prop where
+ space : Space s
+ inputs : ∀ input ∈ inputs, InputReady s input.1 input.2
+
+theorem Ready.keeps {s t : State} (h : Ready s) (k : Keeps s t) : Ready t :=
+ ⟨h.space.keeps k, fun p hp => (h.inputs p hp).keeps k⟩
+
+structure Related (s t : State) : Prop where
+ left : Ready s
+ right : Ready t
+ bp : s.gpr .rbp = t.gpr .rbp
+ bx : s.gpr .rbx = t.gpr .rbx
+ sp : s.gpr .rsp = t.gpr .rsp
+ words : ∀ d ∈ slots, wordAt s d = wordAt t d
+
+theorem Related.keeps {s₁ s₂ t₁ t₂ : State} (h : Related s₁ s₂)
+ (k₁ : Keeps s₁ t₁) (k₂ : Keeps s₂ t₂) : Related t₁ t₂ := by
+ refine ⟨h.left.keeps k₁, h.right.keeps k₂, ?_, ?_, ?_, ?_⟩
+ · rw [k₁.rbp, k₂.rbp, h.bp]
+ · rw [k₁.rbx, k₂.rbx, h.bx]
+ · rw [k₁.rsp, k₂.rsp, h.sp]
+ · intro d hd
+ have bound : ∀ d ∈ slots, d + 8 ≤ 272 := by decide
+ rw [h.left.space.word_keeps k₁ d (bound d hd), h.right.space.word_keeps k₂ d (bound d hd)]
+ exact h.words d hd
+
+def RelatedRegs (rs : List Reg) (s t : State) : Prop :=
+ Related s t ∧ HPrime.AgreeRegs rs s t
+
+theorem hash_keeps_rel {P : State → State → Prop} {c : Prog isa} (rs : List Reg)
+ (saved : ∀ r ∈ rs, r ∈ calleeSaved)
+ (ct : RelCT isa P c (fun _ _ => True))
+ (pre : ∀ s t, P s t → RelatedRegs rs s t)
+ (wp : ∀ s t, P s t → WP isa c s (HPrime.Keeps s) ∧ WP isa c t (HPrime.Keeps t)) :
+ RelCT isa P c (RelatedRegs rs) := by
+ apply (ct.wpDep wp).mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ha, hb⟩
+ have h := pre s t hp
+ refine ⟨h.1.keeps (Keeps.of_hash ha) (Keeps.of_hash hb), ?_⟩
+ intro r hr
+ rw [ha.regs r (saved r hr), hb.regs r (saved r hr)]
+ exact h.2 r hr
+
+theorem finalize_ready {s : State} (h : Ready s) : HPrime.FinalizeReady s :=
+ ⟨h.space.work, h.space.stackWork⟩
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialFinishCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialFinishCT.lean
new file mode 100644
index 000000000..c0cc0dba6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialFinishCT.lean
@@ -0,0 +1,58 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialCTState
+
+/-! H₀ finalization and its fixed-size digest copy reveal no input contents. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.Initial
+
+theorem finishCount_rel : RelCT isa (RelatedRegs [.r12]) (.block [.mov .rsi (.reg .r12)])
+ (fun s t => RelatedRegs [.r12] s t ∧ s.gpr .rsi = t.gpr .rsi) := by
+ have ct := (RelCT.taint (A := taint) (P := RelatedRegs [.r12]) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block [.mov .rsi (.reg .r12)]) (by taint_decide)).wpDep
+ (fun s t _ => ⟨finishCount_ok s, finishCount_ok t⟩)
+ apply ct.mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ⟨ca, _, ka⟩, ⟨cb, _, kb⟩⟩
+ refine ⟨⟨hp.1.keeps (Keeps.of_hash ka) (Keeps.of_hash kb), ?_⟩, ?_⟩
+ · intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ rw [ka.regs .r12 (by decide), kb.regs .r12 (by decide)]
+ exact hp.2 _ (by simp)
+ · rw [ca, cb]; exact hp.2 _ (by simp)
+
+theorem finalize_hash_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa (fun s t => RelatedRegs [.r12] s t ∧ s.gpr .rsi = t.gpr .rsi)
+ (Impl.Argon2.X86_64.HPrime.finalize (HPrime.hash v)) Related := by
+ have ct := HPrime.finalize_rel v (P := fun s t => RelatedRegs [.r12] s t ∧ s.gpr .rsi = t.gpr .rsi)
+ (fun s t h => ⟨finalize_ready h.1.1.left, finalize_ready h.1.1.right, h.1.1.bx, h.2, h.1.1.sp⟩)
+ have result := hash_keeps_rel [] (by simp) ct (fun _ _ h => ⟨h.1.1, by simp [HPrime.AgreeRegs]⟩)
+ (fun s t h => ⟨HPrime.finalize_keeps v s (finalize_ready h.1.1.left),
+ HPrime.finalize_keeps v t (finalize_ready h.1.1.right)⟩)
+ exact result.mono (fun _ _ h => h) (fun _ _ h => h.1)
+
+theorem finishOutput_rel : RelCT isa Related
+ (.block [.mov .r14 (.reg .rbp), .mov32 .rax (.imm 64)])
+ (HPrime.AgreeRegs [.rbx, .r14, .rax]) := by
+ have ct := (RelCT.taint (A := taint) (P := Related) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block [.mov .r14 (.reg .rbp), .mov32 .rax (.imm 64)]) (by taint_decide)).wpDep
+ (fun s t _ => ⟨finishOutput_ok s, finishOutput_ok t⟩)
+ apply ct.mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ⟨da, la, _, ka⟩, ⟨db, lb, _, kb⟩⟩ r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · rw [ka.rbx, kb.rbx, hp.bx]
+ · rw [da, db, hp.bp]
+ · rw [la, lb]
+
+theorem copy_digest_rel : RelCT isa (HPrime.AgreeRegs [.rbx, .r14, .rax])
+ Impl.Argon2.X86_64.HPrime.copy (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbx, .r14, .rax])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+theorem finish_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa (RelatedRegs [.r12]) (finish (HPrime.hash v)) (fun _ _ => True) :=
+ finishCount_rel.seq ((finalize_hash_rel v).seq (finishOutput_rel.seq copy_digest_rel))
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialMetadata.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialMetadata.lean
new file mode 100644
index 000000000..0251d6b4e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialMetadata.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.Initial
+
+/-! H₀ writes its digest into the frame while retaining all enclosing arguments. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64
+
+theorem Finished.rbp {s t : State} (h : Finished s t) : t.gpr .rbp = s.gpr .rbp :=
+ h.regs _ (by decide) (by decide) (by decide)
+
+theorem Finished.rbx {s t : State} (h : Finished s t) : t.gpr .rbx = s.gpr .rbx :=
+ h.regs _ (by decide) (by decide) (by decide)
+
+theorem Finished.rsp {s t : State} (h : Finished s t) : t.gpr .rsp = s.gpr .rsp :=
+ h.regs _ (by decide) (by decide) (by decide)
+
+theorem Finished.frame_word {s t : State} (h : Finished s t) (space : Space s)
+ (d : Nat) (bound : d + 8 ≤ 272) (afterDigest : 64 ≤ d) : wordAt t d = wordAt s d := by
+ unfold wordAt
+ rw [h.rbp]
+ apply h.frame.readW (r := ⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩)
+ (Region.contains_self _ _) ?_ (by decide)
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact space.frameWork.sub_left (Offset.sub_base _ bound) |>.sub_right
+ (Region.sub_prefix (by decide))
+ · exact space.frameStack.sub_left (Offset.sub_base _ bound)
+ · simpa only [BitVec.add_zero] using
+ Offset.disjoint (s.gpr .rbp) (d := d) (n := 8) (e := 0) (k := 64)
+ (Or.inr afterDigest) (by omega) (by decide)
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialStartCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialStartCT.lean
new file mode 100644
index 000000000..42a7be946
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialStartCT.lean
@@ -0,0 +1,74 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialCTState
+
+/-! H₀ initialization and its fixed parameter header have input-independent traces. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.Initial
+
+theorem digestLength_rel : RelCT isa Related (.block [.mov32 .rsi (.imm 64)])
+ (fun s t => Related s t ∧ s.gpr .rsi = 64 ∧ t.gpr .rsi = 64) := by
+ have ct := (RelCT.taint (A := taint) (P := Related) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block [.mov32 .rsi (.imm 64)]) (by taint_decide)).wpDep
+ (fun s t _ => ⟨digestLength_ok s, digestLength_ok t⟩)
+ apply ct.mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ⟨la, _, ka⟩, ⟨lb, _, kb⟩⟩
+ exact ⟨hp.keeps (Keeps.of_hash ka) (Keeps.of_hash kb), la, lb⟩
+
+theorem init_hash_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa (fun s t => Related s t ∧ s.gpr .rsi = 64 ∧ t.gpr .rsi = 64)
+ (Impl.Argon2.X86_64.HPrime.init (HPrime.hash v)) Related := by
+ have ready (s : State) (h : Ready s) (len : s.gpr .rsi = 64) : HPrime.InitReady s :=
+ ⟨by rw [len]; decide, h.space.work,
+ (h.space.stackWork.sub_left (below_sub (by decide) (by decide))).sub_right (Region.sub_prefix (by decide))⟩
+ have ct := HPrime.init_rel v (P := fun s t => Related s t ∧ s.gpr .rsi = 64 ∧ t.gpr .rsi = 64)
+ (fun s t ⟨h, ls, lt⟩ => ⟨ready s h.left ls, ready t h.right lt,
+ h.bx, ls.trans lt.symm, h.sp⟩)
+ have result := hash_keeps_rel [] (by simp) ct (fun _ _ h => ⟨h.1, by simp [HPrime.AgreeRegs]⟩)
+ (fun s t ⟨h, ls, lt⟩ => ⟨HPrime.init_keeps v s (ready s h.left ls), HPrime.init_keeps v t (ready t h.right lt)⟩)
+ exact result.mono (fun _ _ h => h) (fun _ _ h => h.1)
+
+theorem header_state_rel : RelCT isa Related headerCode Related := by
+ have ct := (header_rel.mono (P' := Related) (fun _ _ h => ⟨h.bp, h.bx⟩)
+ (fun _ _ h => h)).wpDep (fun s t h =>
+ ⟨headerWords_ok s 6 (by decide) h.left.space, headerWords_ok t 6 (by decide) h.right.space⟩)
+ exact ct.mono (fun _ _ h => h) (fun _ _ ⟨_, _, _, hp, ⟨_, ka⟩, ⟨_, kb⟩⟩ => hp.keeps ka kb)
+
+theorem fixed_header_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa Related (Impl.Argon2.X86_64.HPrime.absorbFixed (HPrime.hash v) 768 24) Related := by
+ have args := (RelCT.taint (A := taint) (P := Related) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block (Impl.Argon2.X86_64.HPrime.fixedArgs 768 24)) (by taint_decide)).wpDep
+ (fun s t _ => ⟨HPrime.fixedArgs_ok s 768 24 (by decide) (by decide),
+ HPrime.fixedArgs_ok t 768 24 (by decide) (by decide)⟩)
+ have call := HPrime.update_rel v (P := fun a b => True ∧ ∃ s t, Related s t ∧
+ HPrime.FixedArgs s a 768 24 ∧ HPrime.FixedArgs t b 768 24)
+ (fun a b ⟨_, s, t, hp, ha, hb⟩ => ⟨HPrime.fixed_ready (finalize_ready hp.left) ha (by decide) (by decide),
+ HPrime.fixed_ready (finalize_ready hp.right) hb (by decide) (by decide),
+ by rw [ha.keeps.rbx, hb.keeps.rbx, hp.bx], by rw [ha.count, hb.count],
+ by rw [ha.data, hb.data, hp.bx], by rw [ha.size, hb.size], by rw [ha.keeps.rsp, hb.keeps.rsp, hp.sp]⟩)
+ have ct := args.seq call
+ have result := hash_keeps_rel [] (by simp) ct (fun _ _ h => ⟨h, by simp [HPrime.AgreeRegs]⟩)
+ (fun s t h => ⟨HPrime.absorbFixed_keeps v s 768 24 (finalize_ready h.left) (by decide) (by decide),
+ HPrime.absorbFixed_keeps v t 768 24 (finalize_ready h.right) (by decide) (by decide)⟩)
+ exact result.mono (fun _ _ h => h) (fun _ _ h => h.1)
+
+theorem initialCount_rel : RelCT isa Related (.block [.mov32 .r12 (.imm 24)]) (RelatedRegs [.r12]) := by
+ have ct := (RelCT.taint (A := taint) (P := Related) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp))
+ (c := .block [.mov32 .r12 (.imm 24)]) (by taint_decide)).wpDep
+ (fun s t _ => ⟨initialCount_ok s, initialCount_ok t⟩)
+ apply ct.mono (fun _ _ h => h)
+ rintro a b ⟨_, s, t, hp, ⟨la, _, ka⟩, ⟨lb, _, kb⟩⟩
+ refine ⟨hp.keeps ka kb, ?_⟩
+ intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ exact la.trans lb.symm
+
+theorem start_rel (v : Proof.Blake2.X86_64.Backend) :
+ RelCT isa Related (start (HPrime.hash v)) (RelatedRegs [.r12]) :=
+ digestLength_rel.seq ((init_hash_rel v).seq (header_state_rel.seq
+ ((fixed_header_rel v).seq initialCount_rel)))
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialUpdateReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialUpdateReady.lean
new file mode 100644
index 000000000..8caa3a2b7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/InitialUpdateReady.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.InitialCTState
+
+/-! Permissions for the two updates of each length-prefixed H₀ input. -/
+
+namespace VG.Proof.Argon2.X86_64.Initial
+
+open VG VG.X86_64
+
+theorem prefix_update_ready {s t : State} {lo : Nat} (h : Space s) (args : LengthArgs s lo t) :
+ HPrime.UpdateReady t := by
+ have k := args.keeps
+ have ht := h.keeps k
+ have len : (t.gpr .rcx).toNat = 4 := by rw [args.size]; rfl
+ refine ⟨ht.work, ?_, ?_, ?_, ht.stackWork, ?_⟩
+ · rw [args.pointer, len, ← k.rbx]
+ apply Covers.of_sub
+ intro r hr
+ simp only [List.mem_singleton] at hr; subst r
+ exact ⟨⟨t.gpr .rbx, 16384⟩, List.mem_append_right _ ht.work, 792, rfl, by change 792 + 4 ≤ 16384; decide⟩
+ · rw [args.pointer, len, k.rbx]
+ exact (Offset.base_disjoint _ (by decide) (by decide)).symm
+ · rw [args.pointer, len, k.rbx]
+ exact Offset.disjoint _ (d := 792) (n := 4) (e := 192) (k := 576) (by decide) (by decide) (by decide)
+ · rw [args.pointer, len, ← k.rbx]
+ exact ht.stackWork.sub_right (Offset.sub_base _ (by decide))
+
+theorem input_update_ready {s t : State} {po lo : Nat} (h : InputReady s po lo)
+ (length : s.gpr .r14 = wordAt s lo) (args : InputArgs s t po) : HPrime.UpdateReady t := by
+ have k := args.keeps
+ have ptr : t.gpr .rdx = wordAt s po := args.pointer
+ have len : t.gpr .rcx = wordAt s lo := args.length.trans length
+ refine ⟨(h.space.keeps k).work, ?_, ?_, ?_, (h.space.keeps k).stackWork, ?_⟩
+ · rw [ptr, len, k.rd, k.wr]; exact h.cover
+ · rw [ptr, len, k.rbx]; exact h.work.sub_right (Region.sub_prefix (by decide))
+ · rw [ptr, len, k.rbx]; exact h.work.sub_right (Offset.sub_base _ (by decide))
+ · rw [ptr, len, k.rsp]; exact h.stack.symm
+
+structure LengthRelated (lo : Nat) (s t : State) : Prop where
+ related : RelatedRegs [.r12, .r14] s t
+ leftLength : s.gpr .r14 = wordAt s lo
+ rightLength : t.gpr .r14 = wordAt t lo
+
+theorem LengthRelated.hash_keeps {lo : Nat} {s t a b : State} (h : LengthRelated lo s t)
+ (bound : lo + 8 ≤ 272) (ka : HPrime.Keeps s a) (kb : HPrime.Keeps t b) : LengthRelated lo a b := by
+ refine ⟨⟨h.related.1.keeps (Keeps.of_hash ka) (Keeps.of_hash kb), ?_⟩, ?_, ?_⟩
+ · intro r hr
+ have saved : ∀ r ∈ ([.r12, .r14] : List Reg), r ∈ calleeSaved := by decide
+ rw [ka.regs r (saved r hr), kb.regs r (saved r hr)]
+ exact h.related.2 r hr
+ · rw [ka.regs .r14 (by decide), h.related.1.left.space.word_keeps (Keeps.of_hash ka) lo bound]
+ exact h.leftLength
+ · rw [kb.regs .r14 (by decide), h.related.1.right.space.word_keeps (Keeps.of_hash kb) lo bound]
+ exact h.rightLength
+
+end VG.Proof.Argon2.X86_64.Initial
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInit.lean
index eaf7c8ff6..a2d02db7e 100644
--- a/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInit.lean
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInit.lean
@@ -40,7 +40,7 @@ theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String)
WP isa (code name (HPrime.hash v)) s fun t =>
Initialized t.mem memory lanes q lanes (bytesAt s.mem (s.gpr .rbp) 64) ∧
t.gpr .rbp = s.gpr .rbp ∧ t.gpr .rbx = s.gpr .rbx ∧ t.gpr .rsp = s.gpr .rsp ∧
- t.rd = s.rd ∧ t.wr = s.wr ∧
+ t.gpr .r13 = BitVec.ofNat 64 (1024 * q) ∧ t.rd = s.rd ∧ t.wr = s.wr ∧
Frame [⟨memory, 1024 * (lanes * q)⟩, ⟨s.gpr .rbx, 16384⟩,
below (s.gpr .rsp) 24, ⟨s.gpr .rbp + 64, 8⟩] s.mem t.mem := by
unfold code lanesSetupCode
@@ -81,7 +81,7 @@ theorem code_ok (v : Proof.Blake2.X86_64.Backend) (name : String)
· intro t ht
refine ⟨ht.initialized, ht.keeps.rbp.trans (bpB.trans bpA),
ht.keeps.rbx.trans (bxB.trans bxA), ht.keeps.rsp.trans (spB.trans spA),
- ht.keeps.rd.trans (hb.rd.trans ha.rd), ht.keeps.wr.trans (hb.wr.trans ha.wr), ?_⟩
+ (ht.keeps.regs .r13 (by decide)).trans hb.stride, ht.keeps.rd.trans (hb.rd.trans ha.rd), ht.keeps.wr.trans (hb.wr.trans ha.wr), ?_⟩
have fb : Frame [⟨memory, 1024 * (lanes * q)⟩, ⟨s.gpr .rbx, 16384⟩,
below (s.gpr .rsp) 24, ⟨s.gpr .rbp + 64, 8⟩] s.mem b.mem := by
rw [hb.mem]
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitDone.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitDone.lean
new file mode 100644
index 000000000..b5d875a33
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitDone.lean
@@ -0,0 +1,45 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitCT
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInitRepresent
+
+/-! Initialization retains its byte stride and every public frame word outside its lane suffix. -/
+
+namespace VG.Proof.Argon2.X86_64.MemoryInit
+
+open VG VG.X86_64 VG.Spec.Argon2
+open VG.Spec.Blake2 (bytesAt)
+
+structure Done (s t : State) (memory : Addr) (lanes q : Nat) : Prop where
+ initialized : Initialized t.mem memory lanes q lanes (bytesAt s.mem (s.gpr .rbp) 64)
+ bp : t.gpr .rbp = s.gpr .rbp
+ bx : t.gpr .rbx = s.gpr .rbx
+ sp : t.gpr .rsp = s.gpr .rsp
+ stride : t.gpr .r13 = BitVec.ofNat 64 (1024 * q)
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨memory, 1024 * (lanes * q)⟩, ⟨s.gpr .rbx, 16384⟩,
+ below (s.gpr .rsp) 24, ⟨s.gpr .rbp + 64, 8⟩] s.mem t.mem
+
+theorem complete_ok (v : Proof.Blake2.X86_64.Backend) (name : String) (s : State)
+ (memory : Addr) (lanes q : Nat) (ready : Ready memory lanes q s)
+ (positive : 1 ≤ lanes) (lanesBound : lanes < 2 ^ 64) (minimum : 2 ≤ q) :
+ WP isa (Impl.Argon2.X86_64.MemoryInit.code name (HPrime.hash v)) s (Done s · memory lanes q) :=
+ (code_ok v name s memory lanes q positive minimum lanesBound ready.space ready.memoryRead ready.lanesRead
+ ready.blocksRead ready.memoryWord ready.lanesWord ready.blocksWord ready.laneLength).mono
+ (fun _ h => ⟨h.1, h.2.1, h.2.2.1, h.2.2.2.1, h.2.2.2.2.1, h.2.2.2.2.2.1, h.2.2.2.2.2.2.1, h.2.2.2.2.2.2.2⟩)
+
+theorem Done.frame_word {s t : State} {memory : Addr} {lanes q : Nat}
+ (space : Space s memory (1024 * (lanes * q))) (done : Done s t memory lanes q)
+ (d : Nat) (bound : d + 8 ≤ 272) (separate : d + 8 ≤ 64 ∨ 72 ≤ d) :
+ t.mem.readW (t.gpr .rbp + BitVec.ofNat 64 d) 64 = s.mem.readW (s.gpr .rbp + BitVec.ofNat 64 d) 64 := by
+ rw [done.bp]
+ have sub : Region.Sub ⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩ ⟨s.gpr .rbp, 272⟩ := Offset.sub_base _ bound
+ exact done.frame.readW (r := ⟨s.gpr .rbp + BitVec.ofNat 64 d, 8⟩) (Region.contains_self _ _) (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl
+ · exact space.frameMatrix.sub_left sub
+ · exact space.frameWork.sub_left sub
+ · exact space.stackFrame.symm.sub_left sub
+ · exact Offset.disjoint (s.gpr .rbp) separate (by omega) (by decide)) (by decide)
+
+end VG.Proof.Argon2.X86_64.MemoryInit
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitRepresent.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitRepresent.lean
new file mode 100644
index 000000000..6ff514d58
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/MemoryInitRepresent.lean
@@ -0,0 +1,21 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.MemoryInit
+import VerifiedGarbage.Proof.Argon2.Matrix
+
+/-! Memory initialization establishes the shared matrix representation invariant. -/
+
+namespace VG.Proof.Argon2.X86_64.MemoryInit
+
+open VG VG.Spec.Argon2
+
+theorem Initialized.represents {m : Mem} {base : Addr} {p : Params} {h0 : List Byte}
+ (positive : 0 < p.lanes) (lanePositive : 0 < p.laneLen)
+ (h : Initialized m base p.lanes p.laneLen p.lanes h0) :
+ Proof.Argon2.Represents m base p.blocks (initMemory p h0).memory := by
+ refine ⟨Proof.Argon2.initMemory_size p h0, ?_⟩
+ intro k hk
+ rw [Array.getElem?_eq_getElem (by rw [Proof.Argon2.initMemory_size]; exact hk), Option.getD_some]
+ unfold Proof.Argon2.matrixCell
+ rw [Nat.mul_comm k 1024]
+ exact h.spec positive lanePositive k hk
+
+end VG.Proof.Argon2.X86_64.MemoryInit
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/Parameters.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/Parameters.lean
new file mode 100644
index 000000000..674a731e9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/Parameters.lean
@@ -0,0 +1,50 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ParametersSteps
+
+/-! Exact rounded lane length using the verified fixed-time divider. -/
+
+namespace VG.Proof.Argon2.X86_64.Parameters
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (s : State) : Prop where
+ memoryRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 176) 8
+ lanesRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 184) 8
+ memoryWord : s.mem.readW (off (s.gpr .rbp) 176) 64 = BitVec.ofNat 64 p.memory
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+ positive : 0 < p.lanes
+ memoryBound : p.memory < 2 ^ 32
+ lanesBound : p.lanes < 2 ^ 24
+
+def changed : List Reg := [.rdi, .rsi] ++ Divide.changed ++ [.r13]
+
+theorem code_ok (s : State) (p : Params) (h : Ready p s) :
+ WP isa Impl.Argon2.X86_64.Parameters.code s fun t =>
+ t.gpr .r13 = BitVec.ofNat 64 p.laneLen ∧ Divide.Keeps changed s t := by
+ unfold Impl.Argon2.X86_64.Parameters.code
+ refine WP.seq ((args_ok s h.memoryRead h.lanesRead).mono ?_)
+ rintro a ⟨memory, lanes, ka⟩
+ have divisor : a.gpr .rsi = BitVec.ofNat 64 (4 * p.lanes) := by
+ rw [lanes, h.lanesWord, show (4 : Addr) = BitVec.ofNat 64 4 from rfl, ← BitVec.ofNat_mul, Nat.mul_comm]
+ have n : (a.gpr .rdi).toNat = p.memory := by
+ rw [memory, h.memoryWord, BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans h.memoryBound (by decide))]
+ have bound : 4 * p.lanes < 2 ^ 32 := by have lanesBound := h.lanesBound; omega
+ have d : (a.gpr .rsi).toNat = 4 * p.lanes := by
+ rw [divisor, BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans bound (by decide))]
+ refine WP.seq ((Divide.code_ok a (by rw [n]; exact h.memoryBound)
+ (by rw [d]; have positive := h.positive; omega) (by rw [d]; exact bound)).mono ?_)
+ rintro b ⟨quotient, _, kb⟩
+ rw [n, d] at quotient
+ have word : b.gpr .r9 = BitVec.ofNat 64 (p.memory / (4 * p.lanes)) := by
+ rw [← quotient]
+ simp only [BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ refine (finish_ok b).mono ?_
+ rintro t ⟨value, kt⟩
+ refine ⟨?_, (ka.mono (by simp [changed])).trans
+ ((kb.mono (by
+ intro r hr
+ simp only [changed, List.mem_append, List.mem_cons, List.not_mem_nil, or_false]
+ exact Or.inl (Or.inr hr))).trans (kt.mono (by simp [changed])))⟩
+ rw [value, word, show (4 : Addr) = BitVec.ofNat 64 4 from rfl, ← BitVec.ofNat_mul,
+ Nat.mul_comm, ← Proof.Argon2.laneLen_eq p h.positive]
+
+end VG.Proof.Argon2.X86_64.Parameters
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersCT.lean
new file mode 100644
index 000000000..7d8b68256
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersCT.lean
@@ -0,0 +1,16 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ParametersLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Rounded-memory computation has a fixed trace, reading only public frame addresses. -/
+
+namespace VG.Proof.Argon2.X86_64.Parameters
+
+open VG VG.X86_64
+
+theorem code_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ Impl.Argon2.X86_64.Parameters.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.Parameters
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersLit.lean
new file mode 100644
index 000000000..c25b17b49
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Parameters
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+
+/-! Checked literal of rounded-memory parameter computation. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.Parameters.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersSteps.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersSteps.lean
new file mode 100644
index 000000000..0efecf6f4
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ParametersSteps.lean
@@ -0,0 +1,46 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.Parameters
+import VerifiedGarbage.Proof.Argon2.X86_64.Divide
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideCT
+import VerifiedGarbage.Proof.Argon2.X86_64.Initialize
+import VerifiedGarbage.Proof.Argon2.Dimensions
+
+/-! Public frame loads and fixed arithmetic for the RFC's rounded memory dimensions. -/
+
+namespace VG.Proof.Argon2.X86_64.Parameters
+
+open VG VG.X86_64
+
+theorem args_ok (s : State)
+ (memoryRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 176) 8)
+ (lanesRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 184) 8) :
+ WP isa (.block Impl.Argon2.X86_64.Parameters.args) s fun t =>
+ t.gpr .rdi = s.mem.readW (off (s.gpr .rbp) 176) 64 ∧
+ t.gpr .rsi = (s.mem.readW (off (s.gpr .rbp) 184) 64) * 4 ∧ Divide.Keeps [.rdi, .rsi] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.Parameters.args, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ execAlu, State.load64, ea_at, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ RegUpd.mem_setReg, RegUpd.rd_setReg, RegUpd.wr_setReg, memoryRead, lanesRead, reduceCtorEq, ite_true, ite_false, Option.map_some, Option.bind_some,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_, ?_⟩
+ · simp only [show (4 : Addr) = 2#64 + 2#64 from rfl, BitVec.mul_add, BitVec.mul_two]
+ · constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem finish_ok (s : State) : WP isa (.block Impl.Argon2.X86_64.Parameters.finish) s fun t =>
+ t.gpr .r13 = s.gpr .r9 * 4 ∧ Divide.Keeps [.r13] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.Parameters.finish, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, ite_true, Option.map_some, Option.bind_some,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨?_, ?_⟩
+ · simp only [show (4 : Addr) = 2#64 + 2#64 from rfl, BitVec.mul_add, BitVec.mul_two]
+ · constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.Parameters
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSource.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSource.lean
new file mode 100644
index 000000000..71754ca3a
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSource.lean
@@ -0,0 +1,100 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourcePrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWordState
+
+/-! Both random sources satisfy the same filling-step postcondition. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def writes (s : State) : List Region := AddressCache.writes s
+
+structure Done (s t : State) (p : Params) (pass lane slice index : Nat) (state : FillState) : Prop where
+ random : t.gpr .rdi = Proof.Argon2.FillStep.random p pass lane slice index state.memory
+ ready : ∃ old, Ready p pass lane slice index old t
+ represented : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks state.memory
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame (writes s) s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem Ready.index_nat {p : Params} {pass lane slice index old : Nat} {s : State}
+ (h : Ready p pass lane slice index old s) : (s.gpr .r15).toNat = index := by
+ rw [h.filling.position.index, ReferenceMap.word_nat index h.filling.bounds.index_bound64]
+
+theorem independent_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (mode : independent p pass slice = true) :
+ WP isa Impl.Argon2.X86_64.AddressCache.code s (Done s · p pass lane slice index state) := by
+ refine (AddressCache.code_ok p pass lane slice old s h.cache.ready).mono ?_
+ intro t done
+ have base : FillKernel.matrix t = FillKernel.matrix s :=
+ done.selected.frame_word h.cache.layout 232 (by decide) (by decide)
+ have nextReady : Ready p pass lane slice index (AddressCache.wanted s) t := by
+ refine ⟨done.selected.filling_ready h.cache.layout h.filling,
+ done.selected.invariant h.cache.ready, ?_⟩
+ rw [base, done.selected.work_eq]; exact h.matrixWork
+ refine ⟨?_, ⟨_, nextReady⟩,
+ done.selected.represents h.cache.layout h.filling h.matrixWork state.memory represented,
+ done.selected.regs, done.selected.rd, done.selected.wr, done.selected.frame, done.selected.mxcsr⟩
+ have random := done.random
+ unfold AddressCache.wanted at random
+ rw [h.index_nat] at random
+ unfold Proof.Argon2.FillStep.random
+ simp only [mode, ite_true]
+ exact random
+
+theorem dependent_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory)
+ (mode : independent p pass slice = false) :
+ WP isa Impl.Argon2.X86_64.DependentWord.code s (Done s · p pass lane slice index state) := by
+ refine (DependentWord.state_ok s p pass lane slice index h.filling state represented mode).mono ?_
+ rintro t ⟨random, _, matrix, keeps⟩
+ refine ⟨random, ⟨old, h.of_keeps keeps⟩, matrix, ?_, keeps.rd, keeps.wr, ?_, keeps.mxcsr⟩
+ · intro r hr
+ apply keeps.regs
+ 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
+ · rw [keeps.mem]; exact Frame.refl _ _
+
+theorem code_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : Ready p pass lane slice index old s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa Impl.Argon2.X86_64.RandomSource.code s (Done s · p pass lane slice index state) := by
+ unfold Impl.Argon2.X86_64.RandomSource.code
+ refine WP.seq ((prepare_ok s p pass lane slice index old h).mono ?_)
+ rintro a ⟨flag, keeps⟩
+ have next := h.of_keeps keeps
+ have representedA : Proof.Argon2.Represents a.mem (FillKernel.matrix a) p.blocks state.memory := by
+ have base : FillKernel.matrix a = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ rw [base, keeps.mem]; exact represented
+ have finish {t : State} (done : Done a t p pass lane slice index state) :
+ Done s t p pass lane slice index state := by
+ refine ⟨done.random, done.ready, done.represented, ?_, done.rd.trans keeps.rd,
+ done.wr.trans keeps.wr, ?_, done.mxcsr.trans keeps.mxcsr⟩
+ · intro r hr
+ have ne : r ∉ ReferenceMap.changed := by
+ 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 (done.regs r hr).trans (keeps.regs r ne)
+ · have frame := done.frame
+ unfold writes AddressCache.writes AddressCalls.work at frame ⊢
+ rw [keeps.mem, keeps.regs .rbp (by decide), keeps.regs .rsp (by decide)] at frame
+ exact frame
+ refine WP.ite (!independent p pass slice) (by simp only [eval, flag]) ?_ ?_
+ · intro mode
+ have dependent : independent p pass slice = false := by
+ cases eq : independent p pass slice <;> simp_all
+ exact (dependent_ok a p pass lane slice index old next state representedA dependent).mono
+ (fun _ done => finish done)
+ · intro mode
+ have independent : independent p pass slice = true := by
+ cases eq : independent p pass slice <;> simp_all
+ exact (independent_ok a p pass lane slice index old next state representedA independent).mono
+ (fun _ done => finish done)
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCT.lean
new file mode 100644
index 000000000..ea40dba74
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCT.lean
@@ -0,0 +1,74 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSourcePrepare
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressModeCT
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheSelectCT
+import VerifiedGarbage.Proof.Argon2.X86_64.DependentWordCT
+
+/-! Source dispatch and cached-word selection use only public addresses and guards. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.RandomSource
+
+structure Related (p : Params) (pass lane slice index old : Nat) (s t : State) : Prop where
+ left : Ready p pass lane slice index old s
+ right : Ready p pass lane slice index old t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+
+theorem Related.of_keeps {p : Params} {pass lane slice index old : Nat} {s t a b : State}
+ (h : Related p pass lane slice index old s t)
+ (ka : Divide.Keeps ReferenceMap.changed s a) (kb : Divide.Keeps ReferenceMap.changed t b) :
+ Related p pass lane slice index old a b := by
+ refine ⟨h.left.of_keeps ka, h.right.of_keeps kb, ?_, ?_, ?_, ?_⟩
+ · rw [ka.regs .rbp (by decide), kb.regs .rbp (by decide)]; exact h.bases
+ · rw [ka.regs .rsp (by decide), kb.regs .rsp (by decide)]; exact h.stacks
+ · unfold FillKernel.matrix
+ rw [ka.mem, kb.mem, ka.regs .rbp (by decide), kb.regs .rbp (by decide)]
+ exact h.matrices
+ · unfold AddressCalls.work
+ rw [ka.mem, kb.mem, ka.regs .rbp (by decide), kb.regs .rbp (by decide)]
+ exact h.work
+
+theorem test_rel : RelCT isa (fun _ _ : State => True) (.block test) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+
+theorem prepare_rel (p : Params) (pass lane slice index old : Nat) :
+ RelCT isa (Related p pass lane slice index old) prepare
+ (fun s t => Related p pass lane slice index old s t ∧ s.zf = t.zf) := by
+ have trace := AddressMode.code_rel.seq test_rel
+ have narrowed := trace.mono (P' := Related p pass lane slice index old)
+ (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := narrowed.wpDep (fun s t h =>
+ ⟨prepare_ok s p pass lane slice index old h.left,
+ prepare_ok t p pass lane slice index old h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨fa, ka⟩, ⟨fb, kb⟩⟩ := h
+ exact ⟨hp.of_keeps ka kb, fa.trans fb.symm⟩
+
+theorem Related.cache {p : Params} {pass lane slice index old : Nat} {s t : State}
+ (h : Related p pass lane slice index old s t) : AddressCache.ReadyRelated p pass lane slice old s t := by
+ refine ⟨h.left.cache.ready, h.right.cache.ready,
+ ⟨⟨⟨h.left.cache.layout, h.right.cache.layout, h.bases, h.stacks, h.work⟩,
+ h.left.cache.reads, h.right.cache.reads⟩, ?_, ?_, h.left.cache.write, h.right.cache.write⟩⟩
+ · exact h.left.filling.position.index.trans h.right.filling.position.index.symm
+ · exact h.left.cache.words.counterWord.trans h.right.cache.words.counterWord.symm
+
+theorem code_rel (p : Params) (pass lane slice index old : Nat) :
+ RelCT isa (Related p pass lane slice index old) code (fun _ _ => True) := by
+ have branches : RelCT isa
+ (fun s t => Related p pass lane slice index old s t ∧ s.zf = t.zf)
+ (.ite .e Impl.Argon2.X86_64.DependentWord.code Impl.Argon2.X86_64.AddressCache.code)
+ (fun _ _ => True) := by
+ apply RelCT.ite (by intro s t h; simp only [eval, h.2])
+ · exact (DependentWord.code_rel p pass lane slice index).mono
+ (fun _ _ h => ⟨h.1.1.left.filling, h.1.1.right.filling, h.1.1.bases, h.1.1.matrices⟩)
+ (fun _ _ h => h)
+ · exact (AddressCache.code_rel p pass lane slice old).mono
+ (fun _ _ h => h.1.1.cache) (fun _ _ h => h)
+ exact (prepare_rel p pass lane slice index old).seq branches
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCounter.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCounter.lean
new file mode 100644
index 000000000..1668022b5
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceCounter.lean
@@ -0,0 +1,37 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSource
+
+/-! The stored address counter remains public after either source. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def counterValue (p : Params) (pass slice index old : Nat) : Addr :=
+ BitVec.ofNat 64 (if independent p pass slice then index / 128 + 1 else old)
+
+theorem counter_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : Ready p pass lane slice index old s) :
+ WP isa Impl.Argon2.X86_64.RandomSource.code s fun t =>
+ t.mem.readW (off (t.gpr .rbp) 8) 64 = counterValue p pass slice index old := by
+ unfold Impl.Argon2.X86_64.RandomSource.code
+ refine WP.seq ((prepare_ok s p pass lane slice index old h).mono ?_)
+ rintro a ⟨flag, keeps⟩
+ have next := h.of_keeps keeps
+ refine WP.ite (!independent p pass slice) (by simp only [eval, flag]) ?_ ?_
+ · intro mode
+ have dependent : independent p pass slice = false := by cases eq : independent p pass slice <;> simp_all
+ refine (DependentWord.code_ok a p pass lane slice index next.filling).mono ?_
+ rintro t ⟨_, saved⟩
+ unfold counterValue
+ simp only [dependent]
+ rw [saved.mem, saved.regs .rbp (by decide)]
+ exact next.cache.words.counterWord
+ · intro mode
+ have independent : independent p pass slice = true := by cases eq : independent p pass slice <;> simp_all
+ refine (AddressCache.code_ok p pass lane slice old a next.cache.ready).mono ?_
+ intro t done
+ unfold counterValue
+ simp only [independent, ite_true]
+ rw [done.selected.counterWord, AddressCache.counter_nat, next.index_nat]
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourcePrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourcePrepare.lean
new file mode 100644
index 000000000..366a67562
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourcePrepare.lean
@@ -0,0 +1,60 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.RandomSource
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressMode
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheInvariant
+import VerifiedGarbage.Proof.Argon2.X86_64.AddressCacheMatrix
+
+/-! Retain the source invariants while selecting the public addressing mode. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.RandomSource
+
+structure Ready (p : Params) (pass lane slice index old : Nat) (s : State) : Prop where
+ filling : FillKernel.Ready p pass lane slice index s
+ cache : AddressCache.Invariant p pass lane slice old s
+ matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩
+
+theorem Ready.of_keeps {p : Params} {pass lane slice index old : Nat} {s t : State}
+ (h : Ready p pass lane slice index old s) (k : Divide.Keeps ReferenceMap.changed s t) :
+ Ready p pass lane slice index old t := by
+ refine ⟨h.filling.of_keeps k, h.cache.of_keeps k, ?_⟩
+ have matrix : FillKernel.matrix t = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [k.mem, k.regs .rbp (by decide)]
+ have work : AddressCalls.work t = AddressCalls.work s := by
+ unfold AddressCalls.work; rw [k.mem, k.regs .rbp (by decide)]
+ rw [matrix, work]; exact h.matrixWork
+
+theorem test_ok (s : State) : WP isa (.block test) s fun t =>
+ t.zf = decide (s.gpr .r10 = 0#64) ∧ Divide.Keeps [] s t := by
+ apply WP.of_runBlock
+ simp only [test, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.zf_arithFlags, show BitVec.signExtend 64 (0 : BitVec 32) = (0 : Addr) from rfl,
+ Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, ?_⟩
+ · change (s.gpr .r10 - 0#64 == 0#64) = decide (s.gpr .r10 = 0#64)
+ rw [BitVec.sub_zero]
+ apply Bool.eq_iff_iff.mpr
+ simp only [beq_iff_eq, decide_eq_true_eq]
+ constructor
+ · intro r _; exact congrFun (RegUpd.gpr_arithFlags _ _ _ _) r
+ all_goals rfl
+
+theorem bool_zero : ∀ b : Bool, decide ((BitVec.ofBool b).setWidth 64 = 0#64) = !b := by
+ decide +kernel
+
+theorem prepare_ok (s : State) (p : Params) (pass lane slice index old : Nat)
+ (h : Ready p pass lane slice index old s) : WP isa prepare s fun t =>
+ t.zf = !independent p pass slice ∧ Divide.Keeps ReferenceMap.changed s t := by
+ unfold prepare
+ refine WP.seq ((AddressMode.code_spec_ok s p pass slice
+ (h.cache.reads 112 (by simp)) (h.cache.reads 0 (by simp))
+ h.cache.words.variantWord h.filling.passWord h.filling.position.slice
+ (Nat.lt_trans h.filling.bounds.passBound (by decide))
+ (Nat.lt_trans h.filling.bounds.sliceBound (by decide))).mono ?_)
+ rintro a ⟨mode, keeps⟩
+ refine (test_ok a).mono ?_
+ rintro t ⟨flag, tested⟩
+ refine ⟨?_, (keeps.mono (by decide)).trans (tested.mono (by decide))⟩
+ rw [flag, mode, bool_zero]
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceState.lean
new file mode 100644
index 000000000..5be3d8155
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/RandomSourceState.lean
@@ -0,0 +1,23 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.RandomSource
+
+/-! Frame words and public allocation pointers survive random-word dispatch. -/
+
+namespace VG.Proof.Argon2.X86_64.RandomSource
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Done.frame_word {s t : State} {p : Params} {pass lane slice index old : Nat} {state : FillState}
+ (h : Ready p pass lane slice index old s) (done : Done s t p pass lane slice index state)
+ (d : Nat) (bound : d + 8 ≤ 272) (separate : d + 8 ≤ 8 ∨ 16 ≤ d) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.regs .rbp (by simp [calleeSaved])]
+ have sub : Region.Sub ⟨off (s.gpr .rbp) d, 8⟩ ⟨s.gpr .rbp, 272⟩ := Offset.sub_base _ bound
+ exact done.frame.readW (r := ⟨off (s.gpr .rbp) d, 8⟩) (Region.contains_self _ _) (by
+ intro r hr
+ simp only [writes, AddressCache.writes, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · exact h.cache.layout.frameWork.sub_left sub
+ · exact h.cache.layout.frameStack.sub_left sub
+ · exact Offset.disjoint _ separate (by omega) (by decide)) (by decide)
+
+end VG.Proof.Argon2.X86_64.RandomSource
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlock.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlock.lean
new file mode 100644
index 000000000..2407b966b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlock.lean
@@ -0,0 +1,34 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.FillWriteCover
+import VerifiedGarbage.Proof.Argon2.FinalReduction
+
+/-! Reuse the verified word loop with allocation-level permissions. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceBlock
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.ReduceBlock
+
+theorem code_ok (s : State)
+ (read : Covers [⟨s.gpr .rsi, 1024⟩] (s.rd ++ s.wr))
+ (write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr)
+ (separate : (⟨s.gpr .rsi, 1024⟩ : Region).Disjoint ⟨s.gpr .rdi, 1024⟩) :
+ WP isa code s fun t =>
+ blockAt t.mem (s.gpr .rdi) = xorBlock (blockAt s.mem (s.gpr .rdi)) (blockAt s.mem (s.gpr .rsi)) ∧
+ Frame [⟨s.gpr .rdi, 1024⟩] s.mem t.mem ∧ CopyKeeps s t ∧ t.mxcsr = s.mxcsr := by
+ let a := s.withRegions [⟨s.gpr .rsi, 1024⟩] [⟨s.gpr .rdi, 1024⟩]
+ obtain ⟨trace, t, run, written, frame, keeps, mx⟩ :=
+ FillWrite.prefix_ok true 128 (by decide) a (by simp [a]) (by simp [a]) separate
+ have cover : Covers (a.rd ++ a.wr) (s.rd ++ s.wr) := by
+ intro q n ⟨r, hr, hc⟩
+ change r ∈ [⟨s.gpr .rsi, 1024⟩, ⟨s.gpr .rdi, 1024⟩] at hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact read q n ⟨_, by simp, hc⟩
+ · obtain ⟨r, hr, hc⟩ := write q n ⟨_, by simp, hc⟩
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ have run' := Exec.widen (rd := s.rd) (wr := s.wr) run cover write
+ simp only [a, State.withRegions_withRegions, State.withRegions_self] at run'
+ refine ⟨trace, t.withRegions s.rd s.wr, run', ?_, frame, ⟨keeps.1, rfl, rfl⟩, mx⟩
+ exact (written_block written).trans (Proof.Argon2.xorBlock_comm _ _)
+
+end VG.Proof.Argon2.X86_64.ReduceBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockCT.lean
new file mode 100644
index 000000000..b37c8ce36
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockCT.lean
@@ -0,0 +1,15 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceBlockLit
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Final block XOR has fixed accesses determined only by its public pointers. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceBlock
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReduceBlock
+
+theorem code_rel : RelCT isa
+ (fun s t => ∀ r ∈ [Reg.rdi, .rsi], s.gpr r = t.gpr r) code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rdi, .rsi])
+ (fun _ _ h => Taint.agree_ofRegs h) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.ReduceBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockLit.lean
new file mode 100644
index 000000000..2f202634c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceBlockLit.lean
@@ -0,0 +1,8 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceBlock
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+
+namespace VG.Impl.Argon2.X86_64.ReduceBlock
+
+materialize_code code
+
+end VG.Impl.Argon2.X86_64.ReduceBlock
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLane.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLane.lean
new file mode 100644
index 000000000..f8c9665d0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLane.lean
@@ -0,0 +1,89 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceLane
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionState
+import VerifiedGarbage.Proof.Argon2.X86_64.FillCompressSetup
+
+/-! A lane reduction writes only the accumulator, retaining every last-lane block. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLane
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Done (s t : State) (p : Params) (memory : Array Block) (acc : Block) : Prop where
+ ready : Ready p t
+ represented : ReductionState.Represents p memory acc t
+ base : matrix t = matrix s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨matrix s, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem code_ok (s : State) (p : Params) (lane : Nat) (h : Ready p s) (active : lane < p.lanes)
+ (laneWord : s.gpr .rbx = BitVec.ofNat 64 lane) (memory : Array Block) (acc : Block)
+ (represented : ReductionState.Represents p memory acc s) :
+ WP isa Impl.Argon2.X86_64.ReduceLane.code s
+ (Done s · p memory (xorBlock acc (memory[Proof.Argon2.lastIndex p lane]?.getD zeroBlock))) := by
+ have lastBounds := Proof.Argon2.lastIndex_bounds p h.positive h.minimum lane active
+ have q : 0 < p.laneLen := by
+ have eq := Proof.Argon2.laneLen_segments p h.positive
+ have minimum := h.minimum
+ omega
+ unfold Impl.Argon2.X86_64.ReduceLane.code
+ refine WP.seq ((ReducePointers.code_ok s lane p.laneLen q h.read laneWord h.length).mono ?_)
+ rintro a ⟨dest, src, keeps⟩
+ have ha := h.of_keeps keeps
+ have rep := represented.of_keeps keeps
+ have base : matrix a = matrix s := by unfold matrix; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ have dest' : a.gpr .rdi = matrix a := dest.trans base.symm
+ have src' : a.gpr .rsi = Proof.Argon2.matrixCell (matrix a) (Proof.Argon2.lastIndex p lane) := by rw [base]; exact src
+ have sourceWrite : Covers [⟨a.gpr .rsi, 1024⟩] a.wr := by rw [src']; exact ha.block_cover _ lastBounds.2
+ have sourceRead : Covers [⟨a.gpr .rsi, 1024⟩] (a.rd ++ a.wr) := by
+ intro x n hx
+ obtain ⟨r, hr, hc⟩ := sourceWrite x n hx
+ exact ⟨r, List.mem_append_right _ hr, hc⟩
+ have destWrite : Covers [⟨a.gpr .rdi, 1024⟩] a.wr := by rw [dest']; exact ha.accumulator_cover
+ have sep : (⟨a.gpr .rsi, 1024⟩ : Region).Disjoint ⟨a.gpr .rdi, 1024⟩ := by
+ rw [src', dest']
+ simpa only [Proof.Argon2.matrixCell, Nat.zero_mul, BitVec.add_zero] using
+ Proof.Argon2.matrixCell_disjoint (matrix a) p.blocks (Proof.Argon2.lastIndex p lane) 0 ha.bound
+ lastBounds.2 (by omega) (by omega)
+ refine (ReduceBlock.code_ok a sourceRead destWrite sep).mono ?_
+ rintro t ⟨written, frame, copied, mx⟩
+ rw [dest'] at frame written
+ have bp : t.gpr .rbp = a.gpr .rbp := copied.1 .rbp (by decide)
+ have base' : matrix t = matrix a := by
+ unfold matrix
+ rw [bp]
+ exact frame.readW (r := ⟨a.gpr .rbp, 272⟩) (Offset.contains_base _ (by decide) (by decide))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact ha.frame.symm.sub_right (Region.sub_prefix (by omega))) (by decide)
+ refine ⟨?_, ?_, base'.trans base, ?_, copied.2.1.trans keeps.rd, copied.2.2.trans keeps.wr,
+ ?_, mx.trans keeps.mxcsr⟩
+ · refine ⟨ha.positive, ha.minimum, ha.bound, ?_, ?_, ?_, (copied.1 .r12 (by decide)).trans ha.length⟩
+ · rw [copied.2.1, copied.2.2, bp]; exact ha.read
+ · rw [base', copied.2.2]; exact ha.write
+ · rw [base', bp]; exact ha.frame
+ · constructor
+ · rw [base', written, src', rep.accumulator, rep.last lane active]
+ · intro j hj
+ rw [base']
+ apply Eq.trans _ (rep.last j hj)
+ apply FillCompress.block_frame frame
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ have bounds := Proof.Argon2.lastIndex_bounds p ha.positive ha.minimum j hj
+ simpa only [Proof.Argon2.matrixCell, Nat.zero_mul, BitVec.add_zero] using
+ Proof.Argon2.matrixCell_disjoint (matrix a) p.blocks (Proof.Argon2.lastIndex p j) 0 ha.bound
+ bounds.2 (by omega) (by omega)
+ · intro r hr
+ have ne : r ≠ .rax := by
+ 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
+ have unchanged : r ∉ ReducePointers.changed := by
+ 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 (copied.1 r ne).trans (keeps.regs r unchanged)
+ · rw [base, keeps.mem] at frame; exact frame
+
+end VG.Proof.Argon2.X86_64.ReduceLane
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLaneCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLaneCT.lean
new file mode 100644
index 000000000..7ac2952b8
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLaneCT.lean
@@ -0,0 +1,45 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLane
+import VerifiedGarbage.Proof.Argon2.X86_64.ReducePointersCT
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceBlockCT
+
+/-! Final lane reduction depends only on public lane coordinates and matrix pointers. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLane
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Related (p : Params) (lane : Nat) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ active : lane < p.lanes
+ leftLane : s.gpr .rbx = BitVec.ofNat 64 lane
+ rightLane : t.gpr .rbx = BitVec.ofNat 64 lane
+ bases : s.gpr .rbp = t.gpr .rbp
+ matrices : matrix s = matrix t
+
+theorem pointers_rel (p : Params) (lane : Nat) :
+ RelCT isa (Related p lane) Impl.Argon2.X86_64.ReducePointers.code
+ (fun s t => ∀ r ∈ [Reg.rdi, .rsi], s.gpr r = t.gpr r) := by
+ have trace := ReducePointers.code_rel.mono (P' := Related p lane) (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => by
+ have q : 0 < p.laneLen := by
+ have eq := Proof.Argon2.laneLen_segments p h.left.positive
+ have minimum := h.left.minimum
+ omega
+ exact ⟨ReducePointers.code_ok s lane p.laneLen q h.left.read h.leftLane h.left.length,
+ ReducePointers.code_ok t lane p.laneLen q h.right.read h.rightLane h.right.length⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨destA, srcA, _⟩, ⟨destB, srcB, _⟩⟩ := h
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl
+ · exact destA.trans (hp.matrices.trans destB.symm)
+ · have bases : s.mem.readW (off (s.gpr .rbp) 232) 64 = t.mem.readW (off (t.gpr .rbp) 232) 64 := hp.matrices
+ rw [srcA, srcB, bases]
+
+theorem code_rel (p : Params) (lane : Nat) :
+ RelCT isa (Related p lane) Impl.Argon2.X86_64.ReduceLane.code (fun _ _ => True) :=
+ (pointers_rel p lane).seq ReduceBlock.code_rel
+
+end VG.Proof.Argon2.X86_64.ReduceLane
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanes.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanes.lean
new file mode 100644
index 000000000..c91eb2a68
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanes.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLanesBody
+
+
+/-! Termination and correctness of the final lane reduction. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLanes
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Finished (s t : State) (p : Params) (memory : Array Block) (acc : Block) : Prop where
+ represented : ReductionState.Represents p memory acc t
+ base : matrix t = matrix s
+ laneWord : t.gpr .rbx = BitVec.ofNat 64 p.lanes
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨matrix s, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → t.gpr r = s.gpr r
+
+theorem Done.finished {s t : State} {p : Params} {lane : Nat} {memory : Array Block} {acc : Block}
+ (h : Done s t p lane memory acc) (last : lane + 1 = p.lanes) : Finished s t p memory acc :=
+ ⟨h.represented, h.base, last ▸ h.laneWord, h.rd, h.wr, h.frame, h.mxcsr, h.regs⟩
+
+theorem Finished.prepend {s a t : State} {p : Params} {lane : Nat} {memory : Array Block} {acc result : Block}
+ (first : Done s a p lane memory acc) (rest : Finished a t p memory result) : Finished s t p memory result := by
+ refine ⟨rest.represented, rest.base.trans first.base, rest.laneWord,
+ rest.rd.trans first.rd, rest.wr.trans first.wr, ?_, rest.mxcsr.trans first.mxcsr, ?_⟩
+ · have frame := rest.frame
+ rw [first.base] at frame
+ exact first.frame.trans frame
+ · intro r hr bx; exact (rest.regs r hr bx).trans (first.regs r hr bx)
+
+theorem loop_ok (count : Nat) (s : State) (p : Params) (lane : Nat) (h : Ready p lane s)
+ (memory : Array Block) (acc : Block) (represented : ReductionState.Represents p memory acc s)
+ (positive : 0 < count) (endLane : lane + count = p.lanes) :
+ WP isa Impl.Argon2.X86_64.ReduceLanes.loop s
+ (Finished s · p memory (Proof.Argon2.reduction p memory lane count acc)) := by
+ induction count generalizing s lane acc with
+ | zero => omega
+ | succ n ih =>
+ obtain ⟨trace, a, run, done⟩ := body_ok s p lane h memory acc represented
+ rw [Proof.Argon2.reduction_succ]
+ cases n with
+ | zero =>
+ have last : lane + 1 = p.lanes := endLane
+ refine ⟨_, a, .loopExit run ?_, done.finished last⟩
+ simp only [eval, done.cf, last, Nat.lt_irrefl, decide_false]
+ | succ n =>
+ have active : lane + 1 < p.lanes := by omega
+ obtain ⟨restTrace, t, restRun, finished⟩ := ih a (lane + 1) (done.next active) _
+ done.represented (by omega) (by omega)
+ refine ⟨_, t, .loopNext run ?_ restRun, finished.prepend done⟩
+ simp only [eval, done.cf, active, decide_true]
+
+end VG.Proof.Argon2.X86_64.ReduceLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBody.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBody.lean
new file mode 100644
index 000000000..f44a401d8
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBody.lean
@@ -0,0 +1,61 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReduceLanes
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionLoopState
+import VerifiedGarbage.Proof.Argon2.X86_64.FillLaneAdvance
+
+/-! One reduction iteration advances a public lane and preserves the accumulator invariant. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLanes
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Ready (p : Params) (lane : Nat) (s : State) : Prop where
+ allocation : ReductionState.Ready p s
+ active : lane < p.lanes
+ lanesBound : p.lanes < 2 ^ 32
+ laneWord : s.gpr .rbx = BitVec.ofNat 64 lane
+ lanesRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 184) 8
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+structure Done (s t : State) (p : Params) (lane : Nat) (memory : Array Block) (acc : Block) : Prop where
+ represented : ReductionState.Represents p memory acc t
+ base : matrix t = matrix s
+ laneWord : t.gpr .rbx = BitVec.ofNat 64 (lane + 1)
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨matrix s, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+ cf : t.cf = decide (lane + 1 < p.lanes)
+ next : lane + 1 < p.lanes → Ready p (lane + 1) t
+
+theorem body_ok (s : State) (p : Params) (lane : Nat) (h : Ready p lane s)
+ (memory : Array Block) (acc : Block) (represented : ReductionState.Represents p memory acc s) :
+ WP isa Impl.Argon2.X86_64.ReduceLanes.body s
+ (Done s · p lane memory (xorBlock acc (memory[Proof.Argon2.lastIndex p lane]?.getD zeroBlock))) := by
+ unfold Impl.Argon2.X86_64.ReduceLanes.body Impl.Argon2.X86_64.ReduceLanes.advance
+ refine WP.seq ((ReduceLane.code_ok s p lane h.allocation h.active h.laneWord memory acc represented).mono ?_)
+ intro a reduced
+ have read : InRegions (a.rd ++ a.wr) (off (a.gpr .rbp) 184) 8 := by
+ rw [reduced.rd, reduced.wr, reduced.regs .rbp (by simp [calleeSaved])]; exact h.lanesRead
+ have word := (ReductionState.frame_word h.allocation reduced 184 (by decide)).trans h.lanesWord
+ refine (FillLanes.advance_ok a read).mono ?_
+ rintro t ⟨value, flag, keeps⟩
+ have bp := keeps.regs .rbp (by decide)
+ have base : matrix t = matrix a := by unfold matrix; rw [keeps.mem, bp]
+ have added : a.gpr .rbx + 1 = BitVec.ofNat 64 (lane + 1) := by
+ rw [reduced.regs .rbx (by simp [calleeSaved]), h.laneWord, BitVec.ofNat_add]; rfl
+ have nextWord := value.trans added
+ refine ⟨reduced.represented.of_state bp keeps.mem, base.trans reduced.base, nextWord,
+ ?_, keeps.rd.trans reduced.rd, keeps.wr.trans reduced.wr, ?_, keeps.mxcsr.trans reduced.mxcsr, ?_, ?_⟩
+ · intro r hr bx
+ exact (keeps.regs r (by simpa only [List.mem_cons, List.not_mem_nil, or_false] using bx)).trans (reduced.regs r hr)
+ · rw [keeps.mem]; exact reduced.frame
+ · rw [flag, added, word, ReferenceMap.word_nat (lane + 1) (by have bound := h.lanesBound; have active := h.active; omega),
+ ReferenceMap.word_nat p.lanes (Nat.lt_trans h.lanesBound (by decide))]
+ · intro active
+ refine ⟨reduced.ready.of_state bp (keeps.regs .r12 (by decide)) keeps.mem keeps.rd keeps.wr,
+ active, h.lanesBound, nextWord, ?_, ?_⟩
+ · rw [keeps.rd, keeps.wr, bp]; exact read
+ · rw [keeps.mem, bp]; exact word
+
+end VG.Proof.Argon2.X86_64.ReduceLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBodyCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBodyCT.lean
new file mode 100644
index 000000000..91cf42f65
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesBodyCT.lean
@@ -0,0 +1,58 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLanesBody
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLaneCT
+
+/-! Reduction visits the same last blocks even when their contents differ. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLanes
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Related (p : Params) (lane : Nat) (leftMemory rightMemory : Array Block) (leftAcc rightAcc : Block)
+ (s t : State) : Prop where
+ left : Ready p lane s
+ right : Ready p lane t
+ bases : s.gpr .rbp = t.gpr .rbp
+ matrices : matrix s = matrix t
+ leftRep : ReductionState.Represents p leftMemory leftAcc s
+ rightRep : ReductionState.Represents p rightMemory rightAcc t
+
+theorem advance_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block Impl.Argon2.X86_64.ReduceLanes.advance) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem body_rel (p : Params) (lane : Nat) (leftMemory rightMemory : Array Block) (leftAcc rightAcc : Block) :
+ RelCT isa (Related p lane leftMemory rightMemory leftAcc rightAcc) Impl.Argon2.X86_64.ReduceLanes.body
+ (fun s t => s.cf = t.cf ∧ (lane + 1 < p.lanes → Related p (lane + 1) leftMemory rightMemory
+ (xorBlock leftAcc (leftMemory[Proof.Argon2.lastIndex p lane]?.getD zeroBlock))
+ (xorBlock rightAcc (rightMemory[Proof.Argon2.lastIndex p lane]?.getD zeroBlock)) s t)) := by
+ intro s t ts tt a b hp ea eb
+ have related : ReduceLane.Related p lane s t :=
+ ⟨hp.left.allocation, hp.right.allocation, hp.left.active, hp.left.laneWord, hp.right.laneWord, hp.bases, hp.matrices⟩
+ cases ea with
+ | seq reduceA advanceA =>
+ cases eb with
+ | seq reduceB advanceB =>
+ obtain ⟨reduceTrace, _⟩ := ReduceLane.code_rel p lane _ _ _ _ _ _ related reduceA reduceB
+ obtain ⟨_, sa, runA, reducedA⟩ := ReduceLane.code_ok s p lane hp.left.allocation hp.left.active
+ hp.left.laneWord leftMemory leftAcc hp.leftRep
+ obtain ⟨_, sb, runB, reducedB⟩ := ReduceLane.code_ok t p lane hp.right.allocation hp.right.active
+ hp.right.laneWord rightMemory rightAcc hp.rightRep
+ obtain ⟨_, rfl⟩ := Exec.det reduceA runA
+ obtain ⟨_, rfl⟩ := Exec.det reduceB runB
+ have bases := (reducedA.regs .rbp (by simp [calleeSaved])).trans
+ (hp.bases.trans (reducedB.regs .rbp (by simp [calleeSaved])).symm)
+ obtain ⟨advanceTrace, _⟩ := advance_rel _ _ _ _ _ _ bases advanceA advanceB
+ obtain ⟨_, a', runA, doneA⟩ := body_ok s p lane hp.left leftMemory leftAcc hp.leftRep
+ obtain ⟨_, b', runB, doneB⟩ := body_ok t p lane hp.right rightMemory rightAcc hp.rightRep
+ obtain ⟨_, rfl⟩ := Exec.det (.seq reduceA advanceA) runA
+ obtain ⟨_, rfl⟩ := Exec.det (.seq reduceB advanceB) runB
+ refine ⟨by rw [reduceTrace, advanceTrace], doneA.cf.trans doneB.cf.symm, ?_⟩
+ intro active
+ refine ⟨doneA.next active, doneB.next active, ?_,
+ doneA.base.trans (hp.matrices.trans doneB.base.symm), doneA.represented, doneB.represented⟩
+ exact (doneA.regs .rbp (by simp [calleeSaved]) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.ReduceLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesCT.lean
new file mode 100644
index 000000000..ecab8f66b
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReduceLanesCT.lean
@@ -0,0 +1,36 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLanesBodyCT
+
+/-! The final reduction leaks only public matrix addresses and the public lane count. -/
+
+namespace VG.Proof.Argon2.X86_64.ReduceLanes
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem loop_rel (p : Params) (lane count : Nat) (leftMemory rightMemory : Array Block) (leftAcc rightAcc : Block)
+ (positive : 0 < count) (endLane : lane + count = p.lanes) :
+ RelCT isa (Related p lane leftMemory rightMemory leftAcc rightAcc) Impl.Argon2.X86_64.ReduceLanes.loop
+ (fun _ _ => True) := by
+ let I := fun n s t => ∃ (lane : Nat) (leftAcc rightAcc : Block), lane + n = p.lanes ∧ 0 < n ∧
+ Related p lane leftMemory rightMemory leftAcc rightAcc s t
+ have steps : ∀ n, RelCT isa (I n) Impl.Argon2.X86_64.ReduceLanes.body fun s t =>
+ isa.eval .b s = isa.eval .b t ∧ (isa.eval .b s = some false → True) ∧
+ (isa.eval .b s = some true → ∃ m < n, I m s t) := by
+ intro n s t ts tt a b hp ea eb
+ obtain ⟨j, la, ra, endLane, positive, hp⟩ := hp
+ cases n with
+ | zero => omega
+ | succ n =>
+ obtain ⟨trace, flags, next⟩ := body_rel p j leftMemory rightMemory la ra _ _ _ _ _ _ hp ea eb
+ obtain ⟨_, a', runA, done⟩ := body_ok s p j hp.left leftMemory la hp.leftRep
+ obtain ⟨_, rfl⟩ := Exec.det ea runA
+ refine ⟨trace, ?_, fun _ => trivial, ?_⟩
+ · simp only [eval, flags]
+ · intro taken
+ have active : j + 1 < p.lanes := by
+ simp only [eval, done.cf, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ exact ⟨n, by omega, j + 1, _, _, by omega, by omega, next active⟩
+ exact (RelCT.loop I steps count).mono
+ (fun _ _ h => ⟨lane, leftAcc, rightAcc, endLane, positive, h⟩) (fun _ _ h => h)
+
+end VG.Proof.Argon2.X86_64.ReduceLanes
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointers.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointers.lean
new file mode 100644
index 000000000..02e7d27b6
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointers.lean
@@ -0,0 +1,69 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReducePointers
+import VerifiedGarbage.Proof.Argon2.X86_64.BlockAddress
+import VerifiedGarbage.Proof.Argon2.X86_64.Initialize
+import VerifiedGarbage.Proof.Argon2.FinalReduction
+
+/-! The last-lane address calculation preserves all callee-saved registers. -/
+
+namespace VG.Proof.Argon2.X86_64.ReducePointers
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.ReducePointers
+
+def changed : List Reg := [.r8, .rax, .rcx, .rdx, .rsi, .rdi]
+
+theorem setup_ok (s : State) (read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 232) 8) :
+ WP isa (.block Impl.Argon2.X86_64.ReducePointers.setup) s fun t =>
+ t.gpr .r8 = s.mem.readW (off (s.gpr .rbp) 232) 64 ∧
+ t.gpr .rax = s.gpr .rbx ∧ t.gpr .rcx = s.gpr .r12 - 1 ∧
+ Divide.Keeps [.r8, .rax, .rcx] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.ReducePointers.setup, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ State.load64, ea_at, read, ite_true, Option.map_some, Option.bind_some, Option.some.injEq,
+ exists_eq_left', execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, reduceCtorEq, ite_false]
+ refine ⟨trivial, trivial, rfl, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem finish_ok (s : State) : WP isa (.block Impl.Argon2.X86_64.ReducePointers.finish) s fun t =>
+ t.gpr .rsi = s.gpr .rax ∧ t.gpr .rdi = s.gpr .r8 ∧ Divide.Keeps [.rsi, .rdi] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.ReducePointers.finish, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, Option.some.injEq, exists_eq_left', RegUpd.gpr_setReg,
+ reduceCtorEq, ite_true, ite_false]
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem code_ok (s : State) (lane q : Nat) (positive : 0 < q)
+ (read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 232) 8)
+ (laneWord : s.gpr .rbx = BitVec.ofNat 64 lane) (lengthWord : s.gpr .r12 = BitVec.ofNat 64 q) :
+ WP isa code s fun t =>
+ t.gpr .rdi = s.mem.readW (off (s.gpr .rbp) 232) 64 ∧
+ t.gpr .rsi = Proof.Argon2.matrixCell (s.mem.readW (off (s.gpr .rbp) 232) 64) ((lane + 1) * q - 1) ∧
+ Divide.Keeps changed s t := by
+ unfold code
+ refine WP.seq ((setup_ok s read).mono ?_)
+ rintro a ⟨base, lan, col, ka⟩
+ have column : a.gpr .rcx = BitVec.ofNat 64 (q - 1) := by
+ rw [col, lengthWord]
+ exact Offset.ofNat_sub_ofNat (by omega : 1 ≤ q)
+ refine WP.seq ((BlockAddress.code_nat_ok a lane (q - 1) q (lan.trans laneWord) column
+ ((ka.regs .r12 (by decide)).trans lengthWord)).mono ?_)
+ rintro b ⟨address, kb⟩
+ refine (finish_ok b).mono ?_
+ rintro t ⟨src, dest, kt⟩
+ refine ⟨dest.trans ((kb.regs .r8 (by decide)).trans base), ?_, ?_⟩
+ · rw [src, address, base]
+ unfold Proof.Argon2.matrixCell
+ have offset : lane * q + (q - 1) = (lane + 1) * q - 1 := by rw [Nat.add_mul, Nat.one_mul]; omega
+ rw [offset]
+ · exact (ka.mono (by simp [changed])).trans
+ ((kb.mono (by simp [changed])).trans (kt.mono (by simp [changed])))
+
+end VG.Proof.Argon2.X86_64.ReducePointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointersCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointersCT.lean
new file mode 100644
index 000000000..a37c47e79
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReducePointersCT.lean
@@ -0,0 +1,18 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReducePointers
+
+/-! Pointer preparation reads one public frame location and performs fixed arithmetic. -/
+
+namespace VG.Proof.Argon2.X86_64.ReducePointers
+
+open VG VG.X86_64
+
+theorem code_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ Impl.Argon2.X86_64.ReducePointers.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+end VG.Proof.Argon2.X86_64.ReducePointers
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionClear.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionClear.lean
new file mode 100644
index 000000000..fcb34b79e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionClear.lean
@@ -0,0 +1,64 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionLoopState
+import VerifiedGarbage.Proof.Argon2.X86_64.ClearBlock
+
+/-! Clear only block zero, preserving the original last blocks in the matrix. -/
+
+namespace VG.Proof.Argon2.X86_64.ReductionState
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Cleared (s t : State) (p : Params) (memory : Array Block) : Prop where
+ ready : Ready p t
+ represented : Represents p memory zeroBlock t
+ base : matrix t = matrix s
+ keeps : CopyKeeps s t
+ frame : Frame [⟨matrix s, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem clear_ok (s : State) (p : Params) (h : Ready p s)
+ (dest : s.gpr .rdi = matrix s) (memory : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks memory) :
+ WP isa Impl.Argon2.X86_64.ClearBlock.code s (Cleared s · p memory) := by
+ have write : Covers [⟨s.gpr .rdi, 1024⟩] s.wr := by rw [dest]; exact h.accumulator_cover
+ refine (ClearBlock.code_ok s write).mono ?_
+ rintro t ⟨zero, frame, keeps, mx⟩
+ rw [dest] at frame zero
+ have bp := keeps.1 .rbp (by decide)
+ have nonempty := Proof.Argon2.lastIndex_bounds p h.positive h.minimum 0 h.positive
+ have metadata (d : Nat) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [bp]
+ exact frame.readW (r := ⟨s.gpr .rbp, 272⟩) (Offset.contains_base _ bound (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact h.frame.symm.sub_right (Region.sub_prefix (by omega))) (by decide)
+ have base : matrix t = matrix s := metadata 232 (by decide)
+ refine ⟨?_, ?_, base, keeps, frame, mx⟩
+ · refine ⟨h.positive, h.minimum, h.bound, ?_, ?_, ?_, (keeps.1 .r12 (by decide)).trans h.length⟩
+ · rw [keeps.2.1, keeps.2.2, bp]; exact h.read
+ · rw [base, keeps.2.2]; exact h.write
+ · rw [base, bp]; exact h.frame
+ · constructor
+ · rw [base]; exact zero
+ · intro lane active
+ rw [base]
+ apply Eq.trans _ (represented.block _ (Proof.Argon2.lastIndex_bounds p h.positive h.minimum lane active).2)
+ apply FillCompress.block_frame frame
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ have bounds := Proof.Argon2.lastIndex_bounds p h.positive h.minimum lane active
+ simpa only [Proof.Argon2.matrixCell, Nat.zero_mul, BitVec.add_zero] using
+ Proof.Argon2.matrixCell_disjoint (matrix s) p.blocks (Proof.Argon2.lastIndex p lane) 0 h.bound
+ bounds.2 (by omega) (by omega)
+
+theorem Cleared.frame_word {p : Params} {s t : State} {memory : Array Block}
+ (ready : Ready p s) (done : Cleared s t p memory) (d : Nat) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.keeps.1 .rbp (by decide)]
+ exact done.frame.readW (r := ⟨s.gpr .rbp, 272⟩) (Offset.contains_base _ bound (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact ready.frame.symm.sub_right (Region.sub_prefix (by
+ have nonempty := Proof.Argon2.lastIndex_bounds p ready.positive ready.minimum 0 ready.positive
+ omega))) (by decide)
+
+end VG.Proof.Argon2.X86_64.ReductionState
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInit.lean
new file mode 100644
index 000000000..2e4be05b9
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInit.lean
@@ -0,0 +1,72 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReductionInit
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionClear
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLanes
+
+/-! Establish the invariant for reducing all lanes, retaining the input matrix's last blocks. -/
+
+namespace VG.Proof.Argon2.X86_64.ReductionInit
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Ready (p : Params) (s : State) : Prop where
+ allocation : ReductionState.Ready p s
+ lanesBound : p.lanes < 2 ^ 32
+ lanesRead : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 184) 8
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+theorem setup_ok (s : State) (read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 232) 8) :
+ WP isa (.block Impl.Argon2.X86_64.ReductionInit.setup) s fun t =>
+ t.gpr .rdi = matrix s ∧ t.gpr .rbx = 0 ∧ Divide.Keeps [.rdi, .rbx] s t := by
+ apply WP.of_runBlock
+ simp only [Impl.Argon2.X86_64.ReductionInit.setup, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ State.load64, ea_at, read, ite_true, Option.map_some, Option.some.injEq, exists_eq_left',
+ RegUpd.gpr_setReg, reduceCtorEq, ite_false]
+ refine ⟨rfl, rfl, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+structure Prepared (s t : State) (p : Params) (memory : Array Block) : Prop where
+ ready : ReduceLanes.Ready p 0 t
+ represented : ReductionState.Represents p memory zeroBlock t
+ base : matrix t = matrix s
+ regs : ∀ r ∈ calleeSaved, r ≠ .rbx → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨matrix s, 1024⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem code_ok (s : State) (p : Params) (h : Ready p s) (memory : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (matrix s) p.blocks memory) :
+ WP isa Impl.Argon2.X86_64.ReductionInit.code s (Prepared s · p memory) := by
+ unfold Impl.Argon2.X86_64.ReductionInit.code
+ refine WP.seq ((setup_ok s h.allocation.read).mono ?_)
+ rintro a ⟨dest, lane, keeps⟩
+ have bp := keeps.regs .rbp (by decide)
+ have base : matrix a = matrix s := by unfold matrix; rw [keeps.mem, bp]
+ have ha := h.allocation.of_state bp (keeps.regs .r12 (by decide)) keeps.mem keeps.rd keeps.wr
+ have rep : Proof.Argon2.Represents a.mem (matrix a) p.blocks memory := by rw [keeps.mem, base]; exact represented
+ refine (ReductionState.clear_ok a p ha (dest.trans base.symm) memory rep).mono ?_
+ intro t cleared
+ refine ⟨⟨cleared.ready, ha.positive, h.lanesBound,
+ (cleared.keeps.1 .rbx (by decide)).trans lane, ?_, ?_⟩,
+ cleared.represented, cleared.base.trans base, ?_, cleared.keeps.2.1.trans keeps.rd,
+ cleared.keeps.2.2.trans keeps.wr, ?_, cleared.mxcsr.trans keeps.mxcsr⟩
+ · rw [cleared.keeps.2.1, cleared.keeps.2.2, cleared.keeps.1 .rbp (by decide), keeps.rd, keeps.wr, bp]
+ exact h.lanesRead
+ · rw [cleared.frame_word ha 184 (by decide), keeps.mem, bp]; exact h.lanesWord
+ · intro r hr bx
+ have ne : r ≠ .rax := by
+ 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
+ have notDest : r ≠ .rdi := by
+ 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 (cleared.keeps.1 r ne).trans (keeps.regs r (by
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or]; exact ⟨notDest, bx⟩))
+ · have frame := cleared.frame
+ rw [base, keeps.mem] at frame; exact frame
+
+end VG.Proof.Argon2.X86_64.ReductionInit
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInitCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInitCT.lean
new file mode 100644
index 000000000..bd5848b51
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionInitCT.lean
@@ -0,0 +1,54 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReductionInit
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLanesCT
+import VerifiedGarbage.Proof.Argon2.X86_64.ClearBlockLit
+
+/-! Clearing the accumulator follows public pointers and visits a fixed block. -/
+
+namespace VG.Proof.Argon2.X86_64.ReductionInit
+
+open VG VG.X86_64 VG.Spec.Argon2 ReductionState
+
+structure Related (p : Params) (leftMemory rightMemory : Array Block) (s t : State) : Prop where
+ left : Ready p s
+ right : Ready p t
+ bases : s.gpr .rbp = t.gpr .rbp
+ matrices : matrix s = matrix t
+ leftMatrix : Proof.Argon2.Represents s.mem (matrix s) p.blocks leftMemory
+ rightMatrix : Proof.Argon2.Represents t.mem (matrix t) p.blocks rightMemory
+
+theorem setup_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block Impl.Argon2.X86_64.ReductionInit.setup) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem clear_rel : RelCT isa (fun s t => s.gpr .rdi = t.gpr .rdi)
+ Impl.Argon2.X86_64.ClearBlock.code (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rdi])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem code_rel (p : Params) (leftMemory rightMemory : Array Block) :
+ RelCT isa (Related p leftMemory rightMemory) Impl.Argon2.X86_64.ReductionInit.code
+ (ReduceLanes.Related p 0 leftMemory rightMemory zeroBlock zeroBlock) := by
+ intro s t ts tt a b hp ea eb
+ cases ea with
+ | seq setupA clearA =>
+ cases eb with
+ | seq setupB clearB =>
+ obtain ⟨setupTrace, _⟩ := setup_rel _ _ _ _ _ _ hp.bases setupA setupB
+ obtain ⟨_, sa, runA, destA, _, _⟩ := setup_ok s hp.left.allocation.read
+ obtain ⟨_, sb, runB, destB, _, _⟩ := setup_ok t hp.right.allocation.read
+ obtain ⟨_, rfl⟩ := Exec.det setupA runA
+ obtain ⟨_, rfl⟩ := Exec.det setupB runB
+ obtain ⟨clearTrace, _⟩ := clear_rel _ _ _ _ _ _ (destA.trans (hp.matrices.trans destB.symm)) clearA clearB
+ obtain ⟨_, a', runA, doneA⟩ := code_ok s p hp.left leftMemory hp.leftMatrix
+ obtain ⟨_, b', runB, doneB⟩ := code_ok t p hp.right rightMemory hp.rightMatrix
+ obtain ⟨_, rfl⟩ := Exec.det (.seq setupA clearA) runA
+ obtain ⟨_, rfl⟩ := Exec.det (.seq setupB clearB) runB
+ refine ⟨by rw [setupTrace, clearTrace], doneA.ready, doneB.ready, ?_,
+ doneA.base.trans (hp.matrices.trans doneB.base.symm), doneA.represented, doneB.represented⟩
+ exact (doneA.regs .rbp (by simp [calleeSaved]) (by decide)).trans
+ (hp.bases.trans (doneB.regs .rbp (by simp [calleeSaved]) (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.ReductionInit
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionLoopState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionLoopState.lean
new file mode 100644
index 000000000..c9f83cce7
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionLoopState.lean
@@ -0,0 +1,36 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceLane
+
+/-! Public loop metadata survives accumulator writes and register-only advancement. -/
+
+namespace VG.Proof.Argon2.X86_64.ReductionState
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+theorem Ready.of_state {p : Params} {s t : State} (h : Ready p s)
+ (bp : t.gpr .rbp = s.gpr .rbp) (length : t.gpr .r12 = s.gpr .r12)
+ (mem : t.mem = s.mem) (rd : t.rd = s.rd) (wr : t.wr = s.wr) : Ready p t := by
+ have base : matrix t = matrix s := by unfold matrix; rw [mem, bp]
+ refine ⟨h.positive, h.minimum, h.bound, ?_, ?_, ?_, length.trans h.length⟩
+ · rw [rd, wr, bp]; exact h.read
+ · rw [base, wr]; exact h.write
+ · rw [base, bp]; exact h.frame
+
+theorem Represents.of_state {p : Params} {s t : State} {memory : Array Block} {acc : Block}
+ (h : Represents p memory acc s) (bp : t.gpr .rbp = s.gpr .rbp) (mem : t.mem = s.mem) :
+ Represents p memory acc t := by
+ have base : matrix t = matrix s := by unfold matrix; rw [mem, bp]
+ constructor
+ · rw [mem, base]; exact h.accumulator
+ · rw [mem, base]; exact h.last
+
+theorem frame_word {p : Params} {s t : State} {memory : Array Block} {acc : Block}
+ (h : Ready p s) (done : ReduceLane.Done s t p memory acc) (d : Nat) (bound : d + 8 ≤ 272) :
+ t.mem.readW (off (t.gpr .rbp) d) 64 = s.mem.readW (off (s.gpr .rbp) d) 64 := by
+ rw [done.regs .rbp (by simp [calleeSaved])]
+ exact done.frame.readW (r := ⟨s.gpr .rbp, 272⟩) (Offset.contains_base _ bound (by omega))
+ (by intro r hr; simp only [List.mem_singleton] at hr; subst r
+ exact h.frame.symm.sub_right (Region.sub_prefix (by
+ have nonempty := Proof.Argon2.lastIndex_bounds p h.positive h.minimum 0 h.positive
+ omega))) (by decide)
+
+end VG.Proof.Argon2.X86_64.ReductionState
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionState.lean
new file mode 100644
index 000000000..d933e214f
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReductionState.lean
@@ -0,0 +1,58 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReduceBlock
+import VerifiedGarbage.Proof.Argon2.X86_64.ReducePointers
+
+/-! Block zero is the accumulator; every lane's last block remains unchanged. -/
+
+namespace VG.Proof.Argon2.X86_64.ReductionState
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+def matrix (s : State) : Addr := s.mem.readW (off (s.gpr .rbp) 232) 64
+
+structure Ready (p : Params) (s : State) : Prop where
+ positive : 0 < p.lanes
+ minimum : 2 ≤ p.segmentLen
+ bound : p.blocks * 1024 < 2 ^ 64
+ read : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 232) 8
+ write : Covers [⟨matrix s, p.blocks * 1024⟩] s.wr
+ frame : (⟨matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨s.gpr .rbp, 272⟩
+ length : s.gpr .r12 = BitVec.ofNat 64 p.laneLen
+
+structure Represents (p : Params) (memory : Array Block) (acc : Block) (s : State) : Prop where
+ accumulator : blockAt s.mem (matrix s) = acc
+ last : ∀ lane < p.lanes,
+ blockAt s.mem (Proof.Argon2.matrixCell (matrix s) (Proof.Argon2.lastIndex p lane)) =
+ memory[Proof.Argon2.lastIndex p lane]?.getD zeroBlock
+
+theorem Ready.block_cover {p : Params} {s : State} (h : Ready p s) (k : Nat) (active : k < p.blocks) :
+ Covers [⟨Proof.Argon2.matrixCell (matrix s) k, 1024⟩] s.wr := by
+ have sub : Covers [⟨Proof.Argon2.matrixCell (matrix s) k, 1024⟩] [⟨matrix s, p.blocks * 1024⟩] :=
+ Covers.of_sub (by
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨matrix s, p.blocks * 1024⟩, by simp, k * 1024, rfl, by change k * 1024 + 1024 ≤ p.blocks * 1024; omega⟩)
+ exact fun a n ha => h.write a n (sub a n ha)
+
+theorem Ready.accumulator_cover {p : Params} {s : State} (h : Ready p s) :
+ Covers [⟨matrix s, 1024⟩] s.wr := by
+ have nonempty := Proof.Argon2.lastIndex_bounds p h.positive h.minimum 0 h.positive
+ simpa only [Proof.Argon2.matrixCell, Nat.zero_mul, BitVec.add_zero] using h.block_cover 0 (by omega)
+
+theorem Ready.of_keeps {p : Params} {s t : State} (h : Ready p s)
+ (k : Divide.Keeps ReducePointers.changed s t) : Ready p t := by
+ have bp := k.regs .rbp (by decide)
+ have base : matrix t = matrix s := by unfold matrix; rw [k.mem, bp]
+ refine ⟨h.positive, h.minimum, h.bound, ?_, ?_, ?_, (k.regs .r12 (by decide)).trans h.length⟩
+ · rw [k.rd, k.wr, bp]; exact h.read
+ · rw [base, k.wr]; exact h.write
+ · rw [base, bp]; exact h.frame
+
+theorem Represents.of_keeps {p : Params} {s t : State} {memory : Array Block} {acc : Block}
+ (h : Represents p memory acc s) (k : Divide.Keeps ReducePointers.changed s t) : Represents p memory acc t := by
+ have base : matrix t = matrix s := by unfold matrix; rw [k.mem, k.regs .rbp (by decide)]
+ constructor
+ · rw [k.mem, base]; exact h.accumulator
+ · rw [k.mem, base]; exact h.last
+
+end VG.Proof.Argon2.X86_64.ReductionState
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMap.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMap.lean
new file mode 100644
index 000000000..45f203cf3
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMap.lean
@@ -0,0 +1,39 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapFinish
+
+/-! Complete reference mapping against the reviewed RFC specification. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+theorem code_ok (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) :
+ WP isa code s (Result p pass lane slice index s) := by
+ unfold code
+ refine WP.seq ((prepareLanes_ok s p pass lane slice index ready).mono ?_)
+ intro a ha
+ refine WP.seq ((window_ok s a p pass lane slice index ready.bounds ha).mono ?_)
+ intro b hb
+ refine WP.seq ((relative_ok s b p pass lane slice index ready.bounds hb).mono ?_)
+ intro c hc
+ exact finish_ok s c p pass lane slice index ready.bounds hc
+
+theorem spec_lane (p : Spec.Argon2.Params) (pass lane slice index : Nat) (random : Addr) :
+ (Spec.Argon2.reference p pass lane slice index random).1 = chosenLane p pass lane slice random := rfl
+
+theorem spec_column (p : Spec.Argon2.Params) (pass lane slice index : Nat) (random : Addr) :
+ (Spec.Argon2.reference p pass lane slice index random).2 =
+ (windowStart p pass slice + relativeValue p pass lane slice index random) % p.laneLen := rfl
+
+theorem code_spec_ok (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) :
+ WP isa code s fun t =>
+ t.gpr .r9 = BitVec.ofNat 64 (Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)).1 ∧
+ t.gpr .rdi = BitVec.ofNat 64 (Spec.Argon2.reference p pass lane slice index (s.gpr .rdi)).2 ∧
+ t.gpr .r11 = s.gpr .rdi ∧ Divide.Keeps changed s t := by
+ refine (code_ok s p pass lane slice index ready).mono ?_
+ intro t h
+ rw [spec_lane, spec_column]
+ exact ⟨h.selected, h.column, h.original, h.keeps⟩
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapArgs.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapArgs.lean
new file mode 100644
index 000000000..d1987e655
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapArgs.lean
@@ -0,0 +1,70 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceMap
+import VerifiedGarbage.Proof.Argon2.X86_64.DivideStep
+
+/-! Register preparation for the reference-index stages. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+theorem pass_ea (s : State) : s.ea { base := .rbp } = s.gpr .rbp := by
+ change s.gpr .rbp + 0#64 = s.gpr .rbp
+ exact BitVec.add_zero _
+
+theorem loadPass_ok (s : State) (read : InRegions (s.rd ++ s.wr) (s.gpr .rbp) 8) :
+ WP isa (.block loadPass) s fun t =>
+ t.gpr .r9 = s.mem.readW (s.gpr .rbp) 64 ∧ Divide.Keeps [.r9] s t := by
+ apply WP.of_runBlock
+ simp only [loadPass, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ pass_ea, State.load64, read, ite_true, Option.map_some, RegUpd.gpr_setReg,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+ all_goals rfl
+
+theorem laneArgs_ok (s : State) : WP isa (.block laneArgs) s fun t =>
+ t.gpr .rdi = s.gpr .r8 ∧ t.gpr .rsi = s.gpr .rbx ∧
+ Divide.Keeps [.rdi, .rsi] s t := by
+ apply WP.of_runBlock
+ simp only [laneArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, RegUpd.gpr_setReg, reduceCtorEq, ite_true, ite_false,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+theorem relativeArgs_ok (s : State) : WP isa (.block relativeArgs) s fun t =>
+ t.gpr .r9 = s.gpr .rdi ∧ t.gpr .rdi = s.gpr .r11 ∧ t.gpr .rsi = s.gpr .r8 ∧
+ Divide.Keeps [.r9, .rdi, .rsi] s t := by
+ apply WP.of_runBlock
+ simp only [relativeArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc,
+ Option.map_some, RegUpd.gpr_setReg, reduceCtorEq, ite_true, ite_false,
+ Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, hr.1, hr.2.1, hr.2.2, ite_false]
+ all_goals rfl
+
+theorem wrapArgs_ok (s : State) : WP isa (.block wrapArgs) s fun t =>
+ t.gpr .rdi = s.gpr .rax + s.gpr .r10 ∧ t.gpr .rsi = s.gpr .r12 ∧
+ Divide.Keeps [.rdi, .rsi] s t := by
+ apply WP.of_runBlock
+ simp only [wrapArgs, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ Option.map_some, Option.bind_some, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags,
+ reduceCtorEq, ite_true, ite_false, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr.1, hr.2, ite_false]
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapCT.lean
new file mode 100644
index 000000000..dd93d0306
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapCT.lean
@@ -0,0 +1,30 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapLaneCT
+import VerifiedGarbage.Proof.Argon2.X86_64.Relative
+import VerifiedGarbage.Proof.Argon2.X86_64.Wrap
+
+/-! Complete reference mapping has no secret-dependent execution trace. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+theorem relativeArgs_secret_rel :
+ RelCT isa (fun _ _ => True) (.block relativeArgs) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp)) (by taint_decide)
+
+theorem wrapArgs_secret_rel :
+ RelCT isa (fun _ _ => True) (.block wrapArgs) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp)) (by taint_decide)
+
+theorem tail_secret_rel :
+ RelCT isa (fun _ _ => True) (.seq relative finish) (fun _ _ => True) :=
+ (relativeArgs_secret_rel.seq Relative.code_secret_rel).seq
+ (wrapArgs_secret_rel.seq Wrap.code_secret_rel)
+
+theorem code_rel (p : Spec.Argon2.Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) code (fun _ _ => True) :=
+ (prepareLanes_rel p pass lane slice index).seq (window_rel.seq tail_secret_rel)
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapFinish.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapFinish.lean
new file mode 100644
index 000000000..3ef35cc99
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapFinish.lean
@@ -0,0 +1,48 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapRelative
+import VerifiedGarbage.Proof.Argon2.X86_64.Wrap
+
+/-! Wrap the selected relative position into the lane's columns. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+structure Result (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ selected : t.gpr .r9 = BitVec.ofNat 64 (chosenLane p pass lane slice (s.gpr .rdi))
+ column : t.gpr .rdi = BitVec.ofNat 64
+ ((windowStart p pass slice + relativeValue p pass lane slice index (s.gpr .rdi)) % p.laneLen)
+ original : t.gpr .r11 = s.gpr .rdi
+ position : Position p lane slice index t
+ keeps : Divide.Keeps changed s t
+
+theorem finish_ok (s a : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (bounds : Bounds p pass lane slice index) (mapped : Mapped p pass lane slice index s a) :
+ WP isa finish a (Result p pass lane slice index s) := by
+ unfold finish
+ refine WP.seq ((wrapArgs_ok a).mono ?_)
+ rintro b ⟨sum, length, kb⟩
+ have sumWord : b.gpr .rdi = BitVec.ofNat 64
+ (windowStart p pass slice + relativeValue p pass lane slice index (s.gpr .rdi)) := by
+ rw [sum, mapped.relative, mapped.start, ← BitVec.ofNat_add, Nat.add_comm]
+ have sumBound : windowStart p pass slice + relativeValue p pass lane slice index (s.gpr .rdi)
+ < 2 ^ 64 := by
+ have small := bounds.sum_bound (s.gpr .rdi)
+ have q := bounds.laneLength_bound
+ omega
+ have sumNat : (b.gpr .rdi).toNat =
+ windowStart p pass slice + relativeValue p pass lane slice index (s.gpr .rdi) := by
+ rw [sumWord, word_nat _ sumBound]
+ have lengthNat : (b.gpr .rsi).toNat = p.laneLen := by
+ rw [length, mapped.position.laneLength,
+ word_nat _ (Nat.lt_trans bounds.laneLength_bound (by decide))]
+ refine (Wrap.code_nat_ok b (by rw [sumNat, lengthNat]; exact bounds.sum_bound _)).mono ?_
+ rintro t ⟨out, kt⟩
+ have kb' : Divide.Keeps changed a b := kb.mono (by decide)
+ have kt' : Divide.Keeps changed b t := kt.mono (by decide)
+ refine ⟨?_, ?_, ?_, mapped.position.of_keeps (kb'.trans kt'),
+ mapped.keeps.trans (kb'.trans kt')⟩
+ · exact (kt.regs .r9 (by decide)).trans ((kb.regs .r9 (by decide)).trans mapped.selected)
+ · rw [out, sumNat, lengthNat]
+ · exact (kt.regs .r11 (by decide)).trans ((kb.regs .r11 (by decide)).trans mapped.original)
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLane.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLane.lean
new file mode 100644
index 000000000..884bbfe01
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLane.lean
@@ -0,0 +1,83 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapState
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceLane
+import VerifiedGarbage.Proof.Argon2.X86_64.FirstLane
+
+/-! Choose the reference lane, restore the pass and prepare the window inputs. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+structure Chosen (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ selected : t.gpr .r8 = BitVec.ofNat 64 (chosenLane p pass lane slice (s.gpr .rdi))
+ pass : t.gpr .r9 = BitVec.ofNat 64 pass
+ original : t.gpr .r11 = s.gpr .rdi
+ position : Position p lane slice index t
+ keeps : Divide.Keeps changed s t
+
+theorem chooseLane_ok (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) :
+ WP isa chooseLane s (Chosen p pass lane slice index s) := by
+ have lanesNat : (s.gpr .rsi).toNat = p.lanes := by
+ rw [ready.lanes, word_nat _ (by have := ready.bounds.lanesBound; omega)]
+ unfold chooseLane
+ refine WP.seq ((ReferenceLane.code_ok s
+ (by rw [lanesNat]; exact ready.bounds.lanesPositive)
+ (by rw [lanesNat]; exact ready.bounds.lanesBound)).mono ?_)
+ rintro a ⟨laneNat, original, ka⟩
+ have ka' : Divide.Keeps changed s a := ka.mono (by decide)
+ have readA : InRegions (a.rd ++ a.wr) (a.gpr .rbp) 8 := by
+ rw [ka'.rd, ka'.wr, ka'.regs .rbp (by decide)]
+ exact ready.passRead
+ have laneWord : a.gpr .r8 = BitVec.ofNat 64 ((s.gpr .rdi >>> 32).toNat % p.lanes) := by
+ calc
+ a.gpr .r8 = BitVec.ofNat 64 (a.gpr .r8).toNat := by simp only [BitVec.ofNat_toNat, BitVec.setWidth_eq]
+ _ = _ := by rw [laneNat, lanesNat]
+ refine WP.seq ((loadPass_ok a readA).mono ?_)
+ rintro b ⟨loaded, kb⟩
+ have kb' : Divide.Keeps changed a b := kb.mono (by decide)
+ have kab := ka'.trans kb'
+ have pb := ready.position.of_keeps kab
+ have passWord : b.gpr .r9 = BitVec.ofNat 64 pass := by
+ rw [loaded, ka'.mem, ka'.regs .rbp (by decide)]
+ exact ready.passWord
+ have passZero : b.gpr .r9 = 0 ↔ pass = 0 := by
+ rw [passWord]
+ exact word_zero _ (by have := ready.bounds.passBound; omega)
+ have sliceZero : b.gpr .r14 = 0 ↔ slice = 0 := by
+ rw [pb.slice]
+ exact word_zero _ (by have := ready.bounds.sliceBound; omega)
+ refine (FirstLane.code_ok b).mono ?_
+ rintro t ⟨out, kt⟩
+ have kt' : Divide.Keeps changed b t := kt.mono (by decide)
+ refine ⟨?_, ?_, ?_, ready.position.of_keeps (kab.trans kt'), kab.trans kt'⟩
+ · rw [out]
+ by_cases position : pass = 0 ∧ slice = 0 <;>
+ simp only [passZero, sliceZero, pb.current, kb.regs .r8 (by decide), laneWord,
+ chosenLane, position, and_self, ite_true, ite_false]
+ · exact (kt.regs .r9 (by decide)).trans passWord
+ · exact (kt.regs .r11 (by decide)).trans ((kb.regs .r11 (by decide)).trans original)
+
+structure Prepared (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ selected : t.gpr .rdi = BitVec.ofNat 64 (chosenLane p pass lane slice (s.gpr .rdi))
+ current : t.gpr .rsi = BitVec.ofNat 64 lane
+ pass : (t.gpr .r9).toNat = pass
+ original : t.gpr .r11 = s.gpr .rdi
+ position : Position p lane slice index t
+ keeps : Divide.Keeps changed s t
+
+theorem prepareLanes_ok (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) :
+ WP isa prepareLanes s (Prepared p pass lane slice index s) := by
+ unfold prepareLanes
+ refine WP.seq ((chooseLane_ok s p pass lane slice index ready).mono ?_)
+ intro a ha
+ refine (laneArgs_ok a).mono ?_
+ rintro t ⟨laneOut, currentOut, kt⟩
+ have kt' : Divide.Keeps changed a t := kt.mono (by decide)
+ refine ⟨laneOut.trans ha.selected, currentOut.trans ha.position.current, ?_,
+ (kt.regs .r11 (by decide)).trans ha.original,
+ ha.position.of_keeps kt', ha.keeps.trans kt'⟩
+ rw [kt.regs .r9 (by decide), ha.pass, word_nat _ (by have := ready.bounds.passBound; omega)]
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLaneCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLaneCT.lean
new file mode 100644
index 000000000..a22007d7c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLaneCT.lean
@@ -0,0 +1,115 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapLane
+import VerifiedGarbage.Proof.Argon2.X86_64.FirstLaneCT
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapWindowCT
+
+/-! Recover the public pass from the frame without exposing the secret word. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+def Related (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop :=
+ Ready p pass lane slice index s ∧ Ready p pass lane slice index t ∧ s.gpr .rbp = t.gpr .rbp
+
+theorem division_keeps (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) :
+ WP isa VG.Impl.Argon2.X86_64.ReferenceLane.code s fun t =>
+ Ready p pass lane slice index t ∧ Divide.Keeps changed s t := by
+ refine (ReferenceLane.code_ok s
+ (by rw [ready.lanes_nat]; exact ready.bounds.lanesPositive)
+ (by rw [ready.lanes_nat]; exact ready.bounds.lanesBound)).mono ?_
+ rintro t ⟨_, _, keeps⟩
+ have k : Divide.Keeps changed s t := keeps.mono (by decide)
+ exact ⟨ready.of_keeps k (keeps.regs .rsi (by decide)), k⟩
+
+theorem division_rel (p : Spec.Argon2.Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index)
+ VG.Impl.Argon2.X86_64.ReferenceLane.code (Related p pass lane slice index) := by
+ have full := (ReferenceLane.code_secret_rel.mono
+ (P' := Related p pass lane slice index) (fun _ _ _ => trivial)
+ (fun _ _ h => h)).wpDep (fun s t hp =>
+ ⟨division_keeps s p pass lane slice index hp.1,
+ division_keeps t p pass lane slice index hp.2.1⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact ⟨ha.1, hb.1, (ha.2.regs .rbp (by decide)).trans
+ (hp.2.2.trans (hb.2.regs .rbp (by decide)).symm)⟩
+
+theorem loadPass_rel : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp)
+ (.block loadPass) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ subst r
+ exact h)) (by taint_decide)
+
+def Loaded (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop :=
+ Related p pass lane slice index s t ∧
+ s.gpr .r9 = BitVec.ofNat 64 pass ∧ t.gpr .r9 = BitVec.ofNat 64 pass
+
+theorem loadPass_public (s : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (ready : Ready p pass lane slice index s) : WP isa (.block loadPass) s fun t =>
+ Ready p pass lane slice index t ∧ t.gpr .r9 = BitVec.ofNat 64 pass ∧
+ Divide.Keeps changed s t := by
+ refine (loadPass_ok s ready.passRead).mono ?_
+ rintro t ⟨loaded, keeps⟩
+ have k : Divide.Keeps changed s t := keeps.mono (by decide)
+ exact ⟨ready.of_keeps k (keeps.regs .rsi (by decide)), loaded.trans ready.passWord, k⟩
+
+theorem loadPass_public_rel (p : Spec.Argon2.Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) (.block loadPass)
+ (Loaded p pass lane slice index) := by
+ have full := (loadPass_rel.mono (P' := Related p pass lane slice index)
+ (fun _ _ h => h.2.2) (fun _ _ h => h)).wpDep (fun s t hp =>
+ ⟨loadPass_public s p pass lane slice index hp.1,
+ loadPass_public t p pass lane slice index hp.2.1⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact ⟨⟨ha.1, hb.1, (ha.2.2.regs .rbp (by decide)).trans
+ (hp.2.2.trans (hb.2.2.regs .rbp (by decide)).symm)⟩, ha.2.1, hb.2.1⟩
+
+theorem firstLane_loaded_rel (p : Spec.Argon2.Params) (pass lane slice index : Nat) :
+ RelCT isa (Loaded p pass lane slice index) VG.Impl.Argon2.X86_64.FirstLane.code
+ (Loaded p pass lane slice index) := by
+ have trace := FirstLane.code_rel.mono (P' := Loaded p pass lane slice index)
+ (fun _ _ hp => ⟨hp.2.1.trans hp.2.2.symm,
+ hp.1.1.position.slice.trans hp.1.2.1.position.slice.symm⟩) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t _ => ⟨FirstLane.code_ok s, FirstLane.code_ok t⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ have ka : Divide.Keeps changed s a := ha.2.mono (by decide)
+ have kb : Divide.Keeps changed t b := hb.2.mono (by decide)
+ refine ⟨⟨hp.1.1.of_keeps ka (ha.2.regs .rsi (by decide)),
+ hp.1.2.1.of_keeps kb (hb.2.regs .rsi (by decide)),
+ (ka.regs .rbp (by decide)).trans (hp.1.2.2.trans (kb.regs .rbp (by decide)).symm)⟩, ?_, ?_⟩
+ · exact (ha.2.regs .r9 (by decide)).trans hp.2.1
+ · exact (hb.2.regs .r9 (by decide)).trans hp.2.2
+
+theorem laneArgs_secret_rel : RelCT isa (fun _ _ => True) (.block laneArgs) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by simp)) (by taint_decide)
+
+theorem prepareLanes_rel (p : Spec.Argon2.Params) (pass lane slice index : Nat) :
+ RelCT isa (Related p pass lane slice index) prepareLanes PublicPosition := by
+ have head := (division_rel p pass lane slice index).seq
+ ((loadPass_public_rel p pass lane slice index).seq (firstLane_loaded_rel p pass lane slice index))
+ have trace := head.seq (laneArgs_secret_rel.mono (fun _ _ _ => trivial) (fun _ _ h => h))
+ have full := trace.wpDep (fun s t hp =>
+ ⟨prepareLanes_ok s p pass lane slice index hp.1,
+ prepareLanes_ok t p pass lane slice index hp.2.1⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, _, _, _, ha, hb⟩ := h
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl
+ · apply BitVec.eq_of_toNat_eq
+ exact ha.pass.trans hb.pass.symm
+ · exact ha.position.slice.trans hb.position.slice.symm
+ · exact ha.position.segmentLength.trans hb.position.segmentLength.symm
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLit.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLit.lean
new file mode 100644
index 000000000..d146119a0
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapLit.lean
@@ -0,0 +1,10 @@
+import VerifiedGarbage.Proof.Framework.X86_64.Lit
+import VerifiedGarbage.Impl.Argon2.X86_64.ReferenceMap
+
+/-! A checked literal for complete reference-index mapping. -/
+
+namespace VG
+
+materialize_code Impl.Argon2.X86_64.ReferenceMap.code
+
+end VG
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapRelative.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapRelative.lean
new file mode 100644
index 000000000..6052f06fe
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapRelative.lean
@@ -0,0 +1,55 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapWindow
+import VerifiedGarbage.Proof.Argon2.X86_64.Relative
+import VerifiedGarbage.Proof.Framework.X86_64.Abi
+
+/-! Apply the squared J₁ mapping while retaining the lane and window start. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+theorem relativeWord_ok (s : State) (positive : 0 < (s.gpr .rsi).toNat)
+ (bound : (s.gpr .rsi).toNat < 2 ^ 32) :
+ WP isa VG.Impl.Argon2.X86_64.Relative.code s fun t =>
+ t.gpr .rax = BitVec.ofNat 64
+ ((s.gpr .rsi).toNat - 1 - (s.gpr .rsi).toNat *
+ ((s.gpr .rdi &&& 0xffffffff).toNat * (s.gpr .rdi &&& 0xffffffff).toNat / 2 ^ 32) /
+ 2 ^ 32) ∧ Divide.Keeps [.rax, .rdx, .rcx] s t := by
+ refine WP.mono_mx (by decide +kernel) (Relative.code_nat_ok s positive bound) ?_
+ rintro t ⟨out, other, mem, rd, wr⟩ mx
+ refine ⟨out, ⟨?_, mem, rd, wr, mx⟩⟩
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false, not_or] at hr
+ exact other r hr.1 hr.2.1 hr.2.2
+
+structure Mapped (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ selected : t.gpr .r9 = BitVec.ofNat 64 (chosenLane p pass lane slice (s.gpr .rdi))
+ relative : t.gpr .rax = BitVec.ofNat 64 (relativeValue p pass lane slice index (s.gpr .rdi))
+ start : t.gpr .r10 = BitVec.ofNat 64 (windowStart p pass slice)
+ original : t.gpr .r11 = s.gpr .rdi
+ position : Position p lane slice index t
+ keeps : Divide.Keeps changed s t
+
+theorem relative_ok (s a : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (bounds : Bounds p pass lane slice index) (counted : Counted p pass lane slice index s a) :
+ WP isa relative a (Mapped p pass lane slice index s) := by
+ unfold relative
+ refine WP.seq ((relativeArgs_ok a).mono ?_)
+ rintro b ⟨selected, random, count, kb⟩
+ have countNat : (b.gpr .rsi).toNat = windowSize p pass lane slice index (s.gpr .rdi) := by
+ rw [count, counted.count, word_nat _ (Nat.lt_trans (bounds.windowSize_bound32 _) (by decide))]
+ have randomWord : b.gpr .rdi = s.gpr .rdi := random.trans counted.original
+ refine (relativeWord_ok b
+ (by rw [countNat]; exact bounds.windowSize_positive _)
+ (by rw [countNat]; exact bounds.windowSize_bound32 _)).mono ?_
+ rintro t ⟨out, kt⟩
+ have kb' : Divide.Keeps changed a b := kb.mono (by decide)
+ have kt' : Divide.Keeps changed b t := kt.mono (by decide)
+ refine ⟨?_, ?_, ?_, ?_, counted.position.of_keeps (kb'.trans kt'),
+ counted.keeps.trans (kb'.trans kt')⟩
+ · exact (kt.regs .r9 (by decide)).trans (selected.trans counted.selected)
+ · simpa only [relativeValue, countNat, randomWord] using out
+ · exact (kt.regs .r10 (by decide)).trans ((kb.regs .r10 (by decide)).trans counted.start)
+ · exact (kt.regs .r11 (by decide)).trans ((kb.regs .r11 (by decide)).trans counted.original)
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapState.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapState.lean
new file mode 100644
index 000000000..593f7b09c
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapState.lean
@@ -0,0 +1,185 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapArgs
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceCount
+
+/-! Parameters and register invariants for the complete reference mapping. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64
+
+/-- Every stage preserves the enclosing loop's callee-saved registers. -/
+def changed : List Reg := [.rax, .rdx, .rcx, .r8, .r9, .r10, .r11, .rdi, .rsi]
+
+structure Bounds (p : Spec.Argon2.Params) (pass lane slice index : Nat) : Prop where
+ lanesPositive : 0 < p.lanes
+ lanesBound : p.lanes < 2 ^ 32
+ memoryMinimum : 8 * p.lanes ≤ p.memory
+ memoryBound : p.memory < 2 ^ 32
+ passBound : pass < 2 ^ 32
+ laneBound : lane < p.lanes
+ sliceBound : slice < 4
+ indexBound : index < p.segmentLen
+ active : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ index
+
+structure Position (p : Spec.Argon2.Params) (lane slice index : Nat) (s : State) : Prop where
+ current : s.gpr .rbx = BitVec.ofNat 64 lane
+ laneLength : s.gpr .r12 = BitVec.ofNat 64 p.laneLen
+ segmentLength : s.gpr .r13 = BitVec.ofNat 64 p.segmentLen
+ slice : s.gpr .r14 = BitVec.ofNat 64 slice
+ index : s.gpr .r15 = BitVec.ofNat 64 index
+
+theorem Position.of_keeps {s t : State} {p : Spec.Argon2.Params} {lane slice index : Nat}
+ (h : Position p lane slice index s) (k : Divide.Keeps changed s t) :
+ Position p lane slice index t :=
+ ⟨(k.regs .rbx (by decide)).trans h.current,
+ (k.regs .r12 (by decide)).trans h.laneLength,
+ (k.regs .r13 (by decide)).trans h.segmentLength,
+ (k.regs .r14 (by decide)).trans h.slice,
+ (k.regs .r15 (by decide)).trans h.index⟩
+
+structure Ready (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s : State) : Prop where
+ bounds : Bounds p pass lane slice index
+ position : Position p lane slice index s
+ lanes : s.gpr .rsi = BitVec.ofNat 64 p.lanes
+ passRead : InRegions (s.rd ++ s.wr) (s.gpr .rbp) 8
+ passWord : s.mem.readW (s.gpr .rbp) 64 = BitVec.ofNat 64 pass
+
+theorem Ready.lanes_nat {p : Spec.Argon2.Params} {pass lane slice index : Nat} {s : State}
+ (h : Ready p pass lane slice index s) : (s.gpr .rsi).toNat = p.lanes := by
+ rw [h.lanes, BitVec.toNat_ofNat, Nat.mod_eq_of_lt (Nat.lt_trans h.bounds.lanesBound (by decide))]
+
+theorem Ready.of_keeps {p : Spec.Argon2.Params} {pass lane slice index : Nat} {s t : State}
+ (h : Ready p pass lane slice index s) (k : Divide.Keeps changed s t)
+ (lanes : t.gpr .rsi = s.gpr .rsi) : Ready p pass lane slice index t := by
+ refine ⟨h.bounds, h.position.of_keeps k, lanes.trans h.lanes, ?_, ?_⟩
+ · rw [k.rd, k.wr, k.regs .rbp (by decide)]
+ exact h.passRead
+ · rw [k.mem, k.regs .rbp (by decide)]
+ exact h.passWord
+
+def chosenLane (p : Spec.Argon2.Params) (pass lane slice : Nat) (random : Addr) : Nat :=
+ if pass = 0 ∧ slice = 0 then lane else (random >>> 32).toNat % p.lanes
+
+theorem chosenLane_bound (p : Spec.Argon2.Params) (pass lane slice : Nat) (random : Addr)
+ (positive : 0 < p.lanes) (bound : lane < p.lanes) :
+ chosenLane p pass lane slice random < p.lanes := by
+ unfold chosenLane
+ split
+ · exact bound
+ · exact Nat.mod_lt _ positive
+
+theorem word_nat (n : Nat) (bound : n < 2 ^ 64) : (BitVec.ofNat 64 n).toNat = n := by
+ rw [BitVec.toNat_ofNat, Nat.mod_eq_of_lt bound]
+
+theorem word_zero (n : Nat) (bound : n < 2 ^ 64) : BitVec.ofNat 64 n = (0 : Addr) ↔ n = 0 := by
+ constructor
+ · intro h
+ have hn := congrArg BitVec.toNat h
+ rw [word_nat n bound] at hn
+ exact hn
+ · intro h; rw [h]; rfl
+
+theorem word_eq (x y : Nat) (hx : x < 2 ^ 64) (hy : y < 2 ^ 64) :
+ BitVec.ofNat 64 x = BitVec.ofNat 64 y ↔ x = y := by
+ constructor
+ · intro h
+ have hn := congrArg BitVec.toNat h
+ rw [word_nat x hx, word_nat y hy] at hn
+ exact hn
+ · intro h; rw [h]
+
+theorem Bounds.laneLength_bound {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : p.laneLen < 2 ^ 32 := by
+ have hb : p.laneLen ≤ p.blocks := by
+ rw [Proof.Argon2.blocks_lanes p h.lanesPositive]
+ exact Nat.le_mul_of_pos_left _ h.lanesPositive
+ exact Nat.lt_of_le_of_lt (Nat.le_trans hb (Proof.Argon2.blocks_le_memory p)) h.memoryBound
+
+theorem Bounds.window_positive {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) :
+ 0 < ReferenceCount.windowBase p pass slice + index ∧
+ (index = 0 → 0 < ReferenceCount.windowBase p pass slice) := by
+ have hp := Proof.Argon2.reference_count_positive p h.lanesPositive h.memoryMinimum
+ pass slice index true h.active (by intro _ _; rfl)
+ rw [ReferenceCount.spec_count] at hp
+ change 0 < ReferenceCount.windowBase p pass slice + index - 1 at hp
+ constructor
+ · omega
+ · intro zero; rw [zero, Nat.add_zero] at hp; omega
+
+def windowSize (p : Spec.Argon2.Params) (pass lane slice index : Nat) (random : Addr) : Nat :=
+ Spec.Argon2.referenceCount p pass slice index (chosenLane p pass lane slice random == lane)
+
+def windowStart (p : Spec.Argon2.Params) (pass slice : Nat) : Nat :=
+ if pass = 0 then 0 else (slice + 1) * p.segmentLen % p.laneLen
+
+def relativeValue (p : Spec.Argon2.Params) (pass lane slice index : Nat) (random : Addr) : Nat :=
+ let count := windowSize p pass lane slice index random
+ let j := (random &&& 0xffffffff).toNat
+ count - 1 - count * (j * j / 2 ^ 32) / 2 ^ 32
+
+theorem Bounds.segment_le_lane {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : p.segmentLen ≤ p.laneLen := by
+ have segments := Proof.Argon2.laneLen_segments p h.lanesPositive
+ omega
+
+theorem Bounds.index_bound64 {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : index < 2 ^ 64 :=
+ Nat.lt_trans (Nat.lt_of_lt_of_le h.indexBound h.segment_le_lane)
+ (Nat.lt_trans h.laneLength_bound (by decide))
+
+theorem Bounds.chosenLane_bound64 {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) (random : Addr) :
+ chosenLane p pass lane slice random < 2 ^ 64 :=
+ Nat.lt_trans (chosenLane_bound p pass lane slice random h.lanesPositive h.laneBound)
+ (Nat.lt_trans h.lanesBound (by decide))
+
+theorem Bounds.lane_bound64 {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : lane < 2 ^ 64 :=
+ Nat.lt_trans h.laneBound (Nat.lt_trans h.lanesBound (by decide))
+
+theorem Bounds.windowSize_positive {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) (random : Addr) :
+ 0 < windowSize p pass lane slice index random := by
+ apply Proof.Argon2.reference_count_positive p h.lanesPositive h.memoryMinimum
+ pass slice index _ h.active
+ intro firstPass firstSlice
+ simp only [chosenLane, firstPass, firstSlice, and_self, ite_true]
+ exact beq_iff_eq.mpr rfl
+
+theorem Bounds.windowSize_bound32 {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) (random : Addr) :
+ windowSize p pass lane slice index random < 2 ^ 32 :=
+ Proof.Argon2.reference_count_32 p h.lanesPositive h.memoryMinimum h.memoryBound
+ pass slice index _ h.sliceBound h.indexBound
+
+theorem Bounds.windowSize_lt_lane {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) (random : Addr) :
+ windowSize p pass lane slice index random < p.laneLen :=
+ Proof.Argon2.reference_count_lt_lane p h.lanesPositive h.memoryMinimum
+ pass slice index _ h.sliceBound h.indexBound
+
+theorem Bounds.laneLength_positive {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : 0 < p.laneLen := by
+ have seg := Proof.Argon2.segmentLen_ge_two p h.lanesPositive h.memoryMinimum
+ have len := Proof.Argon2.laneLen_segments p h.lanesPositive
+ omega
+
+theorem Bounds.windowStart_lt_lane {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) : windowStart p pass slice < p.laneLen := by
+ unfold windowStart
+ split
+ · exact h.laneLength_positive
+ · exact Nat.mod_lt _ h.laneLength_positive
+
+theorem Bounds.sum_bound {p : Spec.Argon2.Params} {pass lane slice index : Nat}
+ (h : Bounds p pass lane slice index) (random : Addr) :
+ windowStart p pass slice + relativeValue p pass lane slice index random < 2 * p.laneLen := by
+ have start := h.windowStart_lt_lane
+ have relative := Proof.Argon2.reference_relative_bound _ (random &&& 0xffffffff).toNat
+ (h.windowSize_positive random)
+ have count := h.windowSize_lt_lane random
+ change relativeValue p pass lane slice index random < windowSize p pass lane slice index random at relative
+ omega
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindow.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindow.lean
new file mode 100644
index 000000000..705491c15
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindow.lean
@@ -0,0 +1,52 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapLane
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceStart
+
+/-! The selected eligible window and its chronological starting column. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+structure Counted (p : Spec.Argon2.Params) (pass lane slice index : Nat) (s t : State) : Prop where
+ selected : t.gpr .rdi = BitVec.ofNat 64 (chosenLane p pass lane slice (s.gpr .rdi))
+ current : t.gpr .rsi = BitVec.ofNat 64 lane
+ count : t.gpr .r8 = BitVec.ofNat 64 (windowSize p pass lane slice index (s.gpr .rdi))
+ start : t.gpr .r10 = BitVec.ofNat 64 (windowStart p pass slice)
+ original : t.gpr .r11 = s.gpr .rdi
+ position : Position p lane slice index t
+ keeps : Divide.Keeps changed s t
+
+theorem window_ok (s a : State) (p : Spec.Argon2.Params) (pass lane slice index : Nat)
+ (bounds : Bounds p pass lane slice index) (prepared : Prepared p pass lane slice index s a) :
+ WP isa window a (Counted p pass lane slice index s) := by
+ have segmentPositive : 0 < p.segmentLen :=
+ Nat.lt_of_lt_of_le (by decide : 0 < 2)
+ (Proof.Argon2.segmentLen_ge_two p bounds.lanesPositive bounds.memoryMinimum)
+ unfold window
+ refine WP.seq ((ReferenceStart.code_nat_ok a p pass slice bounds.lanesPositive
+ segmentPositive bounds.sliceBound prepared.pass prepared.position.slice
+ prepared.position.segmentLength).mono ?_)
+ rintro b ⟨startWord, kb⟩
+ have kb' : Divide.Keeps changed a b := kb.mono (by decide)
+ have pb := prepared.position.of_keeps kb'
+ have passB : (b.gpr .r9).toNat = pass := by
+ rw [kb.regs .r9 (by decide), prepared.pass]
+ have same : decide (b.gpr .rdi = b.gpr .rsi) =
+ (chosenLane p pass lane slice (s.gpr .rdi) == lane) := by
+ apply Bool.eq_iff_iff.mpr
+ simp only [decide_eq_true_eq, beq_iff_eq]
+ rw [kb.regs .rdi (by decide), kb.regs .rsi (by decide), prepared.selected, prepared.current]
+ exact word_eq _ _ (bounds.chosenLane_bound64 _) bounds.lane_bound64
+ refine (ReferenceCount.code_nat_ok b p pass slice index passB pb.laneLength
+ pb.segmentLength pb.slice pb.index bounds.segment_le_lane bounds.index_bound64
+ bounds.window_positive.1 bounds.window_positive.2).mono ?_
+ rintro t ⟨countWord, kt⟩
+ have kt' : Divide.Keeps changed b t := kt.mono (by decide)
+ refine ⟨?_, ?_, ?_, ?_, ?_, pb.of_keeps kt', prepared.keeps.trans (kb'.trans kt')⟩
+ · exact (kt.regs .rdi (by decide)).trans ((kb.regs .rdi (by decide)).trans prepared.selected)
+ · exact (kt.regs .rsi (by decide)).trans ((kb.regs .rsi (by decide)).trans prepared.current)
+ · simpa only [windowSize, same] using countWord
+ · exact (kt.regs .r10 (by decide)).trans startWord
+ · exact (kt.regs .r11 (by decide)).trans ((kb.regs .r11 (by decide)).trans prepared.original)
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindowCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindowCT.lean
new file mode 100644
index 000000000..84f7f741e
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/ReferenceMapWindowCT.lean
@@ -0,0 +1,24 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceMapState
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceStart
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceStartCT
+import VerifiedGarbage.Proof.Argon2.X86_64.ReferenceCount
+
+/-! The chronological window branches only on the public pass and slice. -/
+
+namespace VG.Proof.Argon2.X86_64.ReferenceMap
+
+open VG VG.X86_64 VG.Impl.Argon2.X86_64.ReferenceMap
+
+def PublicPosition (s t : State) : Prop :=
+ ∀ r ∈ [Reg.r9, .r14, .r13], s.gpr r = t.gpr r
+
+theorem window_rel : RelCT isa PublicPosition window (fun _ _ => True) := by
+ have start := ReferenceStart.code_rel.wpDep (fun s t _ =>
+ ⟨ReferenceStart.code_ok s, ReferenceStart.code_ok t⟩)
+ refine start.seq (ReferenceCount.code_rel.mono ?_ (fun _ _ h => h))
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ exact (ha.2.regs .r9 (by decide)).trans
+ ((hp .r9 (by simp)).trans (hb.2.regs .r9 (by decide)).symm)
+
+end VG.Proof.Argon2.X86_64.ReferenceMap
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetup.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetup.lean
new file mode 100644
index 000000000..0362c2dd5
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetup.lean
@@ -0,0 +1,43 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupCheck
+import VerifiedGarbage.Proof.Argon2.SegmentStart
+
+/-! Fill one complete segment, including the initialized prefix and empty suffix. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+theorem code_ok (s : State) (p : Params) (pass lane slice : Nat)
+ (h : Ready p pass lane slice s) (state : FillState)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks state.memory) :
+ WP isa code s (FillSegment.Finished s · p pass lane slice
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen state)) := by
+ rw [Proof.Argon2.segment_start p pass lane slice state h.parameters.segment_bound.1]
+ change WP isa code s (FillSegment.Finished s · p pass lane slice
+ (Proof.Argon2.segment p pass lane slice (start pass slice) (p.segmentLen - start pass slice) state))
+ unfold code
+ refine WP.seq ((prepare_ok s p pass lane slice h).mono ?_)
+ intro a prepared
+ refine WP.seq ((check_prepared_ok prepared).mono ?_)
+ rintro b ⟨prepared, flag⟩
+ have matrix := prepared.represents h state.memory represented
+ refine WP.ite (decide (start pass slice < p.segmentLen)) (by simp only [eval, flag]) ?_ ?_
+ · intro taken
+ have bound := of_decide_eq_true taken
+ have active := prepared.context.activate bound (start_active pass slice)
+ refine (FillSegment.loop_ok (p.segmentLen - start pass slice) b p pass lane slice (start pass slice) 0
+ active state matrix (by omega) (by omega)).mono ?_
+ intro t finished
+ exact finished_prepared prepared finished
+ · intro skipped
+ have bound := of_decide_eq_false skipped
+ have minimum := h.parameters.segment_bound.1
+ have last : start pass slice = p.segmentLen := by have := start_le pass slice p.segmentLen minimum; omega
+ have finished : FillSegment.Finished b b p pass lane slice state :=
+ ⟨matrix, rfl, rfl, last ▸ prepared.context.position, prepared.context.layout,
+ ⟨0, prepared.context.cache⟩, prepared.context.matrixWork, prepared.context.cache.words.passWord,
+ prepared.context.lanesWord, fun _ _ _ => rfl, rfl, rfl, Frame.refl _ _, rfl⟩
+ rw [last, Nat.sub_self, Proof.Argon2.segment_zero]
+ exact WP.block_nil (finished_prepared prepared finished)
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCT.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCT.lean
new file mode 100644
index 000000000..7fa183e8d
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCT.lean
@@ -0,0 +1,120 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupTrace
+import VerifiedGarbage.Proof.Argon2.X86_64.FillSegmentCT
+
+/-! Complete segment setup and filling expose only the specified reference log. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+structure Related (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) (s t : State) : Prop where
+ ready : RelatedReady p pass lane slice s t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.segment p pass lane slice 0 p.segmentLen leftState).indices =
+ (Proof.Argon2.segment p pass lane slice 0 p.segmentLen rightState).indices
+
+structure PreparedRelated (p : Params) (pass lane slice : Nat) (leftState rightState : FillState)
+ (s t : State) : Prop where
+ left : FillContext.Ready p pass lane slice (start pass slice) 0 s
+ right : FillContext.Ready p pass lane slice (start pass slice) 0 t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+ leftMatrix : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks leftState.memory
+ rightMatrix : Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks rightState.memory
+ indices : (Proof.Argon2.segment p pass lane slice (start pass slice) (p.segmentLen - start pass slice) leftState).indices =
+ (Proof.Argon2.segment p pass lane slice (start pass slice) (p.segmentLen - start pass slice) rightState).indices
+
+theorem prepare_public_rel (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass lane slice leftState rightState) prepare
+ (PreparedRelated p pass lane slice leftState rightState) := by
+ have trace := (prepare_trace p pass lane slice).mono
+ (P' := Related p pass lane slice leftState rightState) (fun _ _ h => h.ready) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨prepare_ok s p pass lane slice h.ready.left, prepare_ok t p pass lane slice h.ready.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨ha.context, hb.context, ?_, ?_, ha.matrix.trans (hp.ready.matrices.trans hb.matrix.symm),
+ ha.work.trans (hp.ready.work.trans hb.work.symm), ha.represents hp.ready.left leftState.memory hp.leftMatrix,
+ hb.represents hp.ready.right rightState.memory hp.rightMatrix, ?_⟩
+ · rw [ha.regs .rbp (by simp [calleeSaved]) (by decide), hb.regs .rbp (by simp [calleeSaved]) (by decide)]
+ exact hp.ready.bases
+ · rw [ha.regs .rsp (by simp [calleeSaved]) (by decide), hb.regs .rsp (by simp [calleeSaved]) (by decide)]
+ exact hp.ready.stacks
+ · have indices := hp.indices
+ rw [Proof.Argon2.segment_start p pass lane slice leftState hp.ready.left.parameters.segment_bound.1,
+ Proof.Argon2.segment_start p pass lane slice rightState hp.ready.right.parameters.segment_bound.1] at indices
+ exact indices
+
+theorem PreparedRelated.of_keeps {p : Params} {pass lane slice : Nat} {leftState rightState : FillState}
+ {s t a b : State} (h : PreparedRelated p pass lane slice leftState rightState s t)
+ (ka : Divide.Keeps [] s a) (kb : Divide.Keeps [] t b) :
+ PreparedRelated p pass lane slice leftState rightState a b := by
+ refine ⟨h.left.of_keeps (ka.mono (by decide)), h.right.of_keeps (kb.mono (by decide)), ?_, ?_, ?_, ?_, ?_, ?_, h.indices⟩
+ · rw [ka.regs .rbp (by simp), kb.regs .rbp (by simp)]; exact h.bases
+ · rw [ka.regs .rsp (by simp), kb.regs .rsp (by simp)]; exact h.stacks
+ · unfold FillKernel.matrix
+ rw [ka.mem, kb.mem, ka.regs .rbp (by simp), kb.regs .rbp (by simp)]; exact h.matrices
+ · unfold AddressCalls.work
+ rw [ka.mem, kb.mem, ka.regs .rbp (by simp), kb.regs .rbp (by simp)]; exact h.work
+ · unfold FillKernel.matrix; rw [ka.mem, ka.regs .rbp (by simp)]; exact h.leftMatrix
+ · unfold FillKernel.matrix; rw [kb.mem, kb.regs .rbp (by simp)]; exact h.rightMatrix
+
+theorem check_context_ok (s : State) (p : Params) (pass lane slice : Nat)
+ (h : FillContext.Ready p pass lane slice (start pass slice) 0 s) : WP isa (.block check) s fun t =>
+ t.cf = decide (start pass slice < p.segmentLen) ∧ Divide.Keeps [] s t := by
+ refine (check_ok s).mono ?_
+ rintro t ⟨flag, keeps⟩
+ have minimum := h.parameters.segment_bound
+ refine ⟨?_, keeps⟩
+ rw [flag, h.position.index, h.position.segmentLength,
+ ReferenceMap.word_nat (start pass slice) (Nat.lt_of_le_of_lt (start_le _ _ _ minimum.1) minimum.2),
+ ReferenceMap.word_nat p.segmentLen minimum.2]
+
+theorem check_trace : RelCT isa (fun _ _ : State => True) (.block check) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+
+theorem check_public_rel (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (PreparedRelated p pass lane slice leftState rightState) (.block check)
+ (fun s t => PreparedRelated p pass lane slice leftState rightState s t ∧
+ s.cf = decide (start pass slice < p.segmentLen) ∧ t.cf = decide (start pass slice < p.segmentLen)) := by
+ have trace := check_trace.mono (P' := PreparedRelated p pass lane slice leftState rightState)
+ (fun _ _ _ => trivial) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨check_context_ok s p pass lane slice h.left, check_context_ok t p pass lane slice h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨fa, ka⟩, ⟨fb, kb⟩⟩ := h
+ exact ⟨hp.of_keeps ka kb, fa, fb⟩
+
+theorem code_rel (p : Params) (pass lane slice : Nat) (leftState rightState : FillState) :
+ RelCT isa (Related p pass lane slice leftState rightState) code (fun _ _ => True) := by
+ have branches : RelCT isa
+ (fun s t => PreparedRelated p pass lane slice leftState rightState s t ∧
+ s.cf = decide (start pass slice < p.segmentLen) ∧ t.cf = decide (start pass slice < p.segmentLen))
+ (.ite .b Impl.Argon2.X86_64.FillSegment.loop (.block [])) (fun _ _ => True) := by
+ refine RelCT.ite (by intro s t h; simp only [eval, h.2.1, h.2.2]) ?_ ?_
+ · intro s t ts tt a b hp ea eb
+ have active : start pass slice < p.segmentLen := by
+ have taken := hp.2
+ simp only [eval, hp.1.2.1, Option.some.injEq, decide_eq_true_eq] at taken
+ exact taken
+ have left := hp.1.1.left.activate active (start_active pass slice)
+ have right := hp.1.1.right.activate active (start_active pass slice)
+ have related : FillSegment.Related p pass lane slice (start pass slice)
+ (p.segmentLen - start pass slice) 0 leftState rightState s t :=
+ ⟨⟨left, right, hp.1.1.bases, hp.1.1.stacks, hp.1.1.matrices, hp.1.1.work⟩,
+ hp.1.1.leftMatrix, hp.1.1.rightMatrix, hp.1.1.indices⟩
+ exact FillSegment.loop_rel p pass lane slice (start pass slice) (p.segmentLen - start pass slice) 0
+ leftState rightState (by omega) (by omega) _ _ _ _ _ _ related ea eb
+ · have noop : RelCT isa (fun _ _ : State => True) (.block []) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+ exact noop.mono (fun _ _ _ => trivial) (fun _ _ h => h)
+ exact (prepare_public_rel p pass lane slice leftState rightState).seq
+ ((check_public_rel p pass lane slice leftState rightState).seq branches)
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCheck.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCheck.lean
new file mode 100644
index 000000000..48fcc0e76
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupCheck.lean
@@ -0,0 +1,49 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupPrepare
+
+/-! Check the prepared index before entering the nonempty segment loop. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+theorem Prepared.of_keeps {p : Params} {pass lane slice : Nat} {s a t : State}
+ (h : Prepared s a p pass lane slice) (k : Divide.Keeps [] a t) : Prepared s t p pass lane slice := by
+ have bp := k.regs .rbp (by decide)
+ have base : FillKernel.matrix t = FillKernel.matrix a := by unfold FillKernel.matrix; rw [k.mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work a := by unfold AddressCalls.work; rw [k.mem, bp]
+ refine ⟨h.context.of_keeps (k.mono (by decide)), base.trans h.matrix, work.trans h.work,
+ ?_, k.rd.trans h.rd, k.wr.trans h.wr, ?_, k.mxcsr.trans h.mxcsr⟩
+ · intro r hr ne; exact (k.regs r (by simp)).trans (h.regs r hr ne)
+ · rw [k.mem]; exact h.frame
+
+theorem check_prepared_ok {p : Params} {pass lane slice : Nat} {s a : State}
+ (h : Prepared s a p pass lane slice) : WP isa (.block check) a fun t =>
+ Prepared s t p pass lane slice ∧ t.cf = decide (start pass slice < p.segmentLen) := by
+ refine (check_ok a).mono ?_
+ rintro t ⟨flag, keeps⟩
+ have minimum := h.context.parameters.segment_bound
+ refine ⟨h.of_keeps keeps, ?_⟩
+ rw [flag, h.context.position.index, h.context.position.segmentLength,
+ ReferenceMap.word_nat (start pass slice) (Nat.lt_of_le_of_lt (start_le _ _ _ minimum.1) minimum.2),
+ ReferenceMap.word_nat p.segmentLen minimum.2]
+
+theorem finished_prepared {p : Params} {pass lane slice : Nat} {s a t : State} {state : FillState}
+ (prepared : Prepared s a p pass lane slice) (finished : FillSegment.Finished a t p pass lane slice state) :
+ FillSegment.Finished s t p pass lane slice state := by
+ refine ⟨finished.represented, finished.matrix.trans prepared.matrix, finished.work.trans prepared.work,
+ finished.position, finished.layout, finished.cache, finished.matrixWork, finished.passWord, finished.lanesWord,
+ ?_, finished.rd.trans prepared.rd, finished.wr.trans prepared.wr, ?_, finished.mxcsr.trans prepared.mxcsr⟩
+ · intro r hr ne; exact (finished.regs r hr ne).trans (prepared.regs r hr ne)
+ · have firstFrame : Frame (FillBlock.writes s p) s.mem a.mem := by
+ apply prepared.frame.sub
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact ⟨⟨off (s.gpr .rbp) 8, 16⟩, by simp [FillBlock.writes], Region.sub_prefix (by decide)⟩
+ have rest := finished.frame
+ rw [FillBlock.writes, prepared.matrix, prepared.work,
+ prepared.regs .rsp (by simp [calleeSaved]) (by decide),
+ prepared.regs .rbp (by simp [calleeSaved]) (by decide)] at rest
+ exact firstFrame.trans rest
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupPrepare.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupPrepare.lean
new file mode 100644
index 000000000..650c27c48
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupPrepare.lean
@@ -0,0 +1,68 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupReset
+
+/-! The prepared context covers ordinary and empty first-segment suffixes. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+structure Prepared (s t : State) (p : Params) (pass lane slice : Nat) : Prop where
+ context : FillContext.Ready p pass lane slice (start pass slice) 0 t
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ regs : ∀ r ∈ calleeSaved, r ≠ .r15 → t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨off (s.gpr .rbp) 8, 8⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem prepare_ok (s : State) (p : Params) (pass lane slice : Nat) (h : Ready p pass lane slice s) :
+ WP isa prepare s (Prepared s · p pass lane slice) := by
+ unfold prepare
+ refine WP.seq ((reset_ok s p pass lane slice h).mono ?_)
+ intro a reset
+ refine (index_ok a pass slice (reset.ready.reads 0 (by simp)) reset.words.passWord reset.words.sliceWord
+ (Nat.lt_trans h.parameters.passBound (by decide))
+ (Nat.lt_trans h.parameters.sliceBound (by decide))).mono ?_
+ rintro t ⟨value, keeps⟩
+ have core := reset.ready.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide)) keeps.mem keeps.rd keeps.wr
+ have cacheA : AddressCache.Invariant p pass lane slice 0 a :=
+ ⟨reset.ready.addressLayout, reset.ready.reads, reset.ready.write, reset.words, by decide, Or.inl rfl⟩
+ have cache := cacheA.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide)) keeps.mem keeps.rd keeps.wr
+ have base : FillKernel.matrix t = FillKernel.matrix a := by
+ unfold FillKernel.matrix; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ have work : AddressCalls.work t = AddressCalls.work a := by
+ unfold AddressCalls.work; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ refine ⟨⟨core.parameters, core.layout, cache, core.matrixWork,
+ ⟨cache.words.laneWord, core.laneLength, core.segmentLength, cache.words.sliceWord, value⟩, core.lanesWord⟩,
+ base.trans reset.matrix, work.trans reset.work, ?_, keeps.rd.trans reset.rd,
+ keeps.wr.trans reset.wr, ?_, keeps.mxcsr.trans reset.mxcsr⟩
+ · intro r hr ne
+ have outside : r ∉ [Reg.rcx, .r15] := by
+ simp only [calleeSaved, List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl | rfl | rfl <;> simp_all
+ exact (keeps.regs r outside).trans (reset.regs r hr)
+ · rw [keeps.mem]; exact reset.frame
+
+theorem Prepared.represents {p : Params} {pass lane slice : Nat} {s t : State}
+ (ready : Ready p pass lane slice s) (h : Prepared s t p pass lane slice) (blocks : Array Block)
+ (represented : Proof.Argon2.Represents s.mem (FillKernel.matrix s) p.blocks blocks) :
+ Proof.Argon2.Represents t.mem (FillKernel.matrix t) p.blocks blocks := by
+ rw [h.matrix]
+ refine ⟨represented.size, ?_⟩
+ intro k hk
+ apply Eq.trans _ (represented.block k hk)
+ apply FillCompress.block_frame h.frame
+ intro r hr
+ simp only [List.mem_singleton] at hr
+ subst r
+ exact (ready.layout.matrixFrame.sub_left (Proof.Argon2.matrixCell_sub _ _ _ hk)).sub_right
+ (Offset.sub_base _ (by decide))
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReady.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReady.lean
new file mode 100644
index 000000000..b2519da98
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReady.lean
@@ -0,0 +1,67 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupSteps
+
+/-! Segment setup needs no previously valid cached block. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2
+
+structure Ready (p : Params) (pass lane slice : Nat) (s : State) : Prop where
+ parameters : FillContext.Parameters p pass lane slice
+ layout : FillKernel.Layout p s
+ addressLayout : AddressCalls.Ready s
+ reads : ∀ d ∈ [0, 8, 72, 112, 240], InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) d) 8
+ write : InRegions s.wr (off (s.gpr .rbp) 8) 8
+ words : ∃ old, AddressHeader.Words p pass lane slice old s
+ matrixWork : (⟨FillKernel.matrix s, p.blocks * 1024⟩ : Region).Disjoint ⟨AddressCalls.work s, 8192⟩
+ laneLength : s.gpr .r12 = BitVec.ofNat 64 p.laneLen
+ segmentLength : s.gpr .r13 = BitVec.ofNat 64 p.segmentLen
+ lanesWord : s.mem.readW (off (s.gpr .rbp) 184) 64 = BitVec.ofNat 64 p.lanes
+
+theorem Ready.of_state {p : Params} {pass lane slice : Nat} {s t : State}
+ (h : Ready p pass lane slice s)
+ (regs : ∀ r ∈ [Reg.rbp, .rsp, .rbx, .r12, .r13, .r14], t.gpr r = s.gpr r)
+ (mem : t.mem = s.mem) (rd : t.rd = s.rd) (wr : t.wr = s.wr) : Ready p pass lane slice t := by
+ have bp := regs .rbp (by simp)
+ have sp := regs .rsp (by simp)
+ have base : FillKernel.matrix t = FillKernel.matrix s := by unfold FillKernel.matrix; rw [mem, bp]
+ have work : AddressCalls.work t = AddressCalls.work s := by unfold AddressCalls.work; rw [mem, bp]
+ refine ⟨h.parameters, h.layout.of_preserved bp sp base work rd wr, ?_, ?_, ?_, ?_, ?_,
+ (regs .r12 (by simp)).trans h.laneLength, (regs .r13 (by simp)).trans h.segmentLength, ?_⟩
+ · constructor
+ · rw [rd, wr, bp]; exact h.addressLayout.frameRead
+ · rw [wr, work]; exact h.addressLayout.workWrite
+ · rw [bp, work]; exact h.addressLayout.frameWork
+ · rw [bp, sp]; exact h.addressLayout.frameStack
+ · rw [sp, work]; exact h.addressLayout.stackWork
+ · rw [rd, wr, bp]; exact h.reads
+ · rw [wr, bp]; exact h.write
+ · obtain ⟨old, words⟩ := h.words
+ refine ⟨old, ?_, (regs .rbx (by simp)).trans words.laneWord,
+ (regs .r14 (by simp)).trans words.sliceWord, ?_, ?_, ?_, ?_⟩
+ all_goals rw [mem, bp]
+ · exact words.passWord
+ · exact words.blocksWord
+ · exact words.passesWord
+ · exact words.variantWord
+ · exact words.counterWord
+ · rw [base, work]; exact h.matrixWork
+ · rw [mem, bp]; exact h.lanesWord
+
+theorem Ready.saved {p : Params} {pass lane slice : Nat} {s t : State}
+ (h : Ready p pass lane slice s) (saved : AddressCache.Saved s t) : Ready p pass lane slice t := by
+ have bp := congrFun saved.regs Reg.rbp
+ have sp := congrFun saved.regs Reg.rsp
+ have base : FillKernel.matrix t = FillKernel.matrix s := saved.read 232 (by decide) (by decide)
+ refine ⟨h.parameters, h.layout.of_preserved bp sp base saved.work_eq saved.rd saved.wr,
+ saved.ready, ?_, ?_, ?_, ?_, ?_, ?_, ?_⟩
+ · rw [saved.rd, saved.wr, bp]; exact h.reads
+ · rw [saved.wr, bp]; exact h.write
+ · obtain ⟨old, words⟩ := h.words
+ exact ⟨(s.gpr .rax).toNat, saved.words words (by simp)⟩
+ · rw [base, saved.work_eq]; exact h.matrixWork
+ · rw [saved.regs]; exact h.laneLength
+ · rw [saved.regs]; exact h.segmentLength
+ · exact (saved.read 184 (by decide) (by decide)).trans h.lanesWord
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReset.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReset.lean
new file mode 100644
index 000000000..0f25ef537
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupReset.lean
@@ -0,0 +1,48 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupReady
+
+/-! Reset the counter while preserving the segment header and matrix allocation. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+structure Reset (s t : State) (p : Params) (pass lane slice : Nat) : Prop where
+ ready : Ready p pass lane slice t
+ words : AddressHeader.Words p pass lane slice 0 t
+ matrix : FillKernel.matrix t = FillKernel.matrix s
+ work : AddressCalls.work t = AddressCalls.work s
+ regs : ∀ r ∈ calleeSaved, t.gpr r = s.gpr r
+ rd : t.rd = s.rd
+ wr : t.wr = s.wr
+ frame : Frame [⟨off (s.gpr .rbp) 8, 8⟩] s.mem t.mem
+ mxcsr : t.mxcsr = s.mxcsr
+
+theorem reset_ok (s : State) (p : Params) (pass lane slice : Nat) (h : Ready p pass lane slice s) :
+ WP isa reset s (Reset s · p pass lane slice) := by
+ unfold reset
+ refine WP.seq ((register_ok s .rax 0).mono ?_)
+ rintro a ⟨value, keeps⟩
+ have next := h.of_state (by
+ intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ rcases hr with rfl | rfl | rfl | rfl | rfl | rfl <;> exact keeps.regs _ (by decide)) keeps.mem keeps.rd keeps.wr
+ refine (AddressCache.save_ready a next.addressLayout next.write).mono ?_
+ intro t saved
+ obtain ⟨old, words⟩ := next.words
+ have matrixA : FillKernel.matrix a = FillKernel.matrix s := by
+ unfold FillKernel.matrix; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ have workA : AddressCalls.work a = AddressCalls.work s := by
+ unfold AddressCalls.work; rw [keeps.mem, keeps.regs .rbp (by decide)]
+ refine ⟨next.saved saved, saved.words words value,
+ (saved.read 232 (by decide) (by decide)).trans matrixA, saved.work_eq.trans workA,
+ ?_, saved.rd.trans keeps.rd, saved.wr.trans keeps.wr, ?_, saved.mxcsr.trans keeps.mxcsr⟩
+ · intro r hr
+ have ne : r ∉ [Reg.rax] := by
+ 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 (congrFun saved.regs r).trans (keeps.regs r ne)
+ · have frame := saved.frame
+ rw [keeps.mem, keeps.regs .rbp (by decide)] at frame
+ exact frame
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupSteps.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupSteps.lean
new file mode 100644
index 000000000..ae237bed1
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupSteps.lean
@@ -0,0 +1,88 @@
+import VerifiedGarbage.Impl.Argon2.X86_64.SegmentSetup
+import VerifiedGarbage.Proof.Argon2.X86_64.FillContext
+
+/-! Select index two only in slice zero of pass zero. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+def start (pass slice : Nat) : Nat := if pass = 0 ∧ slice = 0 then 2 else 0
+
+theorem start_active (pass slice : Nat) : pass ≠ 0 ∨ slice ≠ 0 ∨ 2 ≤ start pass slice := by
+ unfold start; split <;> omega
+
+theorem start_le (pass slice : Nat) (g : Nat) (minimum : 2 ≤ g) : start pass slice ≤ g := by
+ unfold start; split <;> omega
+
+theorem first_ok (s : State) (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8) :
+ WP isa (.block first) s fun t =>
+ t.zf = decide (s.mem.readW (off (s.gpr .rbp) 0) 64 = 0#64 ∧ s.gpr .r14 = 0#64) ∧
+ Divide.Keeps [.rcx] s t := by
+ apply WP.of_runBlock
+ simp only [first, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, State.load64,
+ ea_at, hr, execAlu, RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, RegUpd.zf_arithFlags,
+ reduceCtorEq, ite_true, ite_false,
+ show BitVec.signExtend 64 (0 : BitVec 32) = (0 : Addr) from rfl,
+ Option.map_some, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨?_, ?_⟩
+ · change ((s.mem.readW (off (s.gpr .rbp) 0) 64 ||| s.gpr .r14) - 0#64 == 0#64) = _
+ rw [BitVec.sub_zero]
+ apply Bool.eq_iff_iff.mpr
+ simp only [beq_iff_eq, BitVec.or_eq_zero_iff, decide_eq_true_eq]
+ · constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, RegUpd.gpr_arithFlags, hr, ite_false]
+ all_goals rfl
+
+theorem register_ok (s : State) (register : Reg) (value : BitVec 32) :
+ WP isa (.block [.mov register (.imm value)]) s fun t =>
+ t.gpr register = value.signExtend 64 ∧ Divide.Keeps [register] s t := by
+ apply WP.of_runBlock
+ simp only [runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, RegUpd.gpr_setReg,
+ ite_true, Option.map_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r hr
+ simp only [List.mem_cons, List.not_mem_nil, or_false] at hr
+ simp only [RegUpd.gpr_setReg, hr, ite_false]
+ all_goals rfl
+
+theorem index_ok (s : State) (pass slice : Nat)
+ (hr : InRegions (s.rd ++ s.wr) (off (s.gpr .rbp) 0) 8)
+ (passWord : s.mem.readW (off (s.gpr .rbp) 0) 64 = BitVec.ofNat 64 pass)
+ (sliceWord : s.gpr .r14 = BitVec.ofNat 64 slice)
+ (passBound : pass < 2 ^ 64) (sliceBound : slice < 2 ^ 64) : WP isa index s fun t =>
+ t.gpr .r15 = BitVec.ofNat 64 (start pass slice) ∧ Divide.Keeps [.rcx, .r15] s t := by
+ unfold index
+ refine WP.seq ((first_ok s hr).mono ?_)
+ rintro a ⟨flag, keeps⟩
+ have firstFlag : a.zf = decide (pass = 0 ∧ slice = 0) := by
+ rw [flag, passWord, sliceWord]
+ have passZero : BitVec.ofNat 64 pass = 0#64 ↔ pass = 0 := ReferenceMap.word_zero pass passBound
+ have sliceZero : BitVec.ofNat 64 slice = 0#64 ↔ slice = 0 := ReferenceMap.word_zero slice sliceBound
+ simp only [passZero, sliceZero]
+ refine WP.ite (decide (pass = 0 ∧ slice = 0)) (by simp only [eval, firstFlag]) ?_ ?_
+ · intro mode
+ refine (register_ok a .r15 2).mono ?_
+ rintro t ⟨value, changed⟩
+ refine ⟨?_, (keeps.mono (by decide)).trans (changed.mono (by decide))⟩
+ unfold start; simp only [of_decide_eq_true mode]; exact value
+ · intro mode
+ refine (register_ok a .r15 0).mono ?_
+ rintro t ⟨value, changed⟩
+ refine ⟨?_, (keeps.mono (by decide)).trans (changed.mono (by decide))⟩
+ unfold start; simp only [of_decide_eq_false mode, ite_false]; exact value
+
+theorem check_ok (s : State) : WP isa (.block check) s fun t =>
+ t.cf = decide ((s.gpr .r15).toNat < (s.gpr .r13).toNat) ∧ Divide.Keeps [] s t := by
+ apply WP.of_runBlock
+ simp only [check, runBlock_cons, runStep_some, runBlock_nil, exec, readSrc, execAlu,
+ RegUpd.cf_arithFlags, Option.bind_some, Option.some.injEq, exists_eq_left']
+ refine ⟨trivial, ?_⟩
+ constructor
+ · intro r _; exact congrFun (RegUpd.gpr_arithFlags _ _ _ _) r
+ all_goals rfl
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupTrace.lean b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupTrace.lean
new file mode 100644
index 000000000..5dc69d065
--- /dev/null
+++ b/lean/VerifiedGarbage/Proof/Argon2/X86_64/SegmentSetupTrace.lean
@@ -0,0 +1,100 @@
+import VerifiedGarbage.Proof.Argon2.X86_64.SegmentSetupPrepare
+import VerifiedGarbage.Proof.Framework.X86_64.RelCT
+
+/-! Cache reset and initial-index selection branch only on public parameters. -/
+
+namespace VG.Proof.Argon2.X86_64.SegmentSetup
+
+open VG VG.X86_64 VG.Spec.Argon2 VG.Impl.Argon2.X86_64.SegmentSetup
+
+structure RelatedReady (p : Params) (pass lane slice : Nat) (s t : State) : Prop where
+ left : Ready p pass lane slice s
+ right : Ready p pass lane slice t
+ bases : s.gpr .rbp = t.gpr .rbp
+ stacks : s.gpr .rsp = t.gpr .rsp
+ matrices : FillKernel.matrix s = FillKernel.matrix t
+ work : AddressCalls.work s = AddressCalls.work t
+
+theorem RelatedReady.of_keeps {p : Params} {pass lane slice : Nat} {s t a b : State}
+ (h : RelatedReady p pass lane slice s t)
+ (ka : Divide.Keeps [.rax, .rcx, .r15] s a) (kb : Divide.Keeps [.rax, .rcx, .r15] t b) :
+ RelatedReady p pass lane slice a b := by
+ have protectedRegs : ∀ r ∈ [Reg.rbp, .rsp, .rbx, .r12, .r13, .r14], r ∉ [Reg.rax, .rcx, .r15] := by decide
+ refine ⟨h.left.of_state (fun r hr => ka.regs r (protectedRegs r hr)) ka.mem ka.rd ka.wr,
+ h.right.of_state (fun r hr => kb.regs r (protectedRegs r hr)) kb.mem kb.rd kb.wr, ?_, ?_, ?_, ?_⟩
+ · rw [ka.regs .rbp (by decide), kb.regs .rbp (by decide)]; exact h.bases
+ · rw [ka.regs .rsp (by decide), kb.regs .rsp (by decide)]; exact h.stacks
+ · unfold FillKernel.matrix
+ rw [ka.mem, kb.mem, ka.regs .rbp (by decide), kb.regs .rbp (by decide)]
+ exact h.matrices
+ · unfold AddressCalls.work
+ rw [ka.mem, kb.mem, ka.regs .rbp (by decide), kb.regs .rbp (by decide)]
+ exact h.work
+
+theorem reset_trace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp) reset (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem reset_public_rel (p : Params) (pass lane slice : Nat) :
+ RelCT isa (RelatedReady p pass lane slice) reset (RelatedReady p pass lane slice) := by
+ have trace := reset_trace.mono (P' := RelatedReady p pass lane slice)
+ (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨reset_ok s p pass lane slice h.left, reset_ok t p pass lane slice h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ha, hb⟩ := h
+ refine ⟨ha.ready, hb.ready, ?_, ?_, ha.matrix.trans (hp.matrices.trans hb.matrix.symm),
+ ha.work.trans (hp.work.trans hb.work.symm)⟩
+ · rw [ha.regs .rbp (by simp [calleeSaved]), hb.regs .rbp (by simp [calleeSaved])]; exact hp.bases
+ · rw [ha.regs .rsp (by simp [calleeSaved]), hb.regs .rsp (by simp [calleeSaved])]; exact hp.stacks
+
+theorem first_spec_ok (s : State) (p : Params) (pass lane slice : Nat) (h : Ready p pass lane slice s) :
+ WP isa (.block first) s fun t => t.zf = decide (pass = 0 ∧ slice = 0) ∧ Divide.Keeps [.rcx] s t := by
+ obtain ⟨old, words⟩ := h.words
+ refine (first_ok s (h.reads 0 (by simp))).mono ?_
+ rintro t ⟨flag, keeps⟩
+ refine ⟨?_, keeps⟩
+ rw [flag, words.passWord, words.sliceWord]
+ have passZero : BitVec.ofNat 64 pass = 0#64 ↔ pass = 0 :=
+ ReferenceMap.word_zero pass (Nat.lt_trans h.parameters.passBound (by decide))
+ have sliceZero : BitVec.ofNat 64 slice = 0#64 ↔ slice = 0 :=
+ ReferenceMap.word_zero slice (Nat.lt_trans h.parameters.sliceBound (by decide))
+ simp only [passZero, sliceZero]
+
+theorem first_trace : RelCT isa (fun s t => s.gpr .rbp = t.gpr .rbp) (.block first) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [.rbp])
+ (fun _ _ h => Taint.agree_ofRegs (by
+ intro r hr; simp only [List.mem_cons, List.not_mem_nil, or_false] at hr; subst r; exact h)) (by taint_decide)
+
+theorem first_public_rel (p : Params) (pass lane slice : Nat) :
+ RelCT isa (RelatedReady p pass lane slice) (.block first)
+ (fun s t => RelatedReady p pass lane slice s t ∧ s.zf = t.zf) := by
+ have trace := first_trace.mono (P' := RelatedReady p pass lane slice)
+ (fun _ _ h => h.bases) (fun _ _ h => h)
+ have full := trace.wpDep (fun s t h => ⟨first_spec_ok s p pass lane slice h.left, first_spec_ok t p pass lane slice h.right⟩)
+ refine full.mono (fun _ _ h => h) ?_
+ intro a b h
+ obtain ⟨_, s, t, hp, ⟨fa, ka⟩, ⟨fb, kb⟩⟩ := h
+ exact ⟨hp.of_keeps (ka.mono (by decide)) (kb.mono (by decide)), fa.trans fb.symm⟩
+
+theorem index_trace (p : Params) (pass lane slice : Nat) :
+ RelCT isa (RelatedReady p pass lane slice) index (fun _ _ => True) := by
+ have two : RelCT isa (fun _ _ : State => True) (.block [.mov .r15 (.imm 2)]) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+ have zero : RelCT isa (fun _ _ : State => True) (.block [.mov .r15 (.imm 0)]) (fun _ _ => True) :=
+ RelCT.taint (A := taint) (Taint.ofRegs [])
+ (fun _ _ _ => Taint.agree_ofRegs (by intro r hr; simp at hr)) (by taint_decide)
+ have branches : RelCT isa (fun s t => RelatedReady p pass lane slice s t ∧ s.zf = t.zf)
+ (.ite .e (.block [.mov .r15 (.imm 2)]) (.block [.mov .r15 (.imm 0)])) (fun _ _ => True) :=
+ RelCT.ite (by intro s t h; simp only [eval, h.2])
+ (two.mono (fun _ _ _ => trivial) (fun _ _ h => h))
+ (zero.mono (fun _ _ _ => trivial) (fun _ _ h => h))
+ exact (first_public_rel p pass lane slice).seq branches
+
+theorem prepare_trace (p : Params) (pass lane slice : Nat) :
+ RelCT isa (RelatedReady p pass lane slice) prepare (fun _ _ => True) :=
+ (reset_public_rel p pass lane slice).seq (index_trace p pass lane slice)
+
+end VG.Proof.Argon2.X86_64.SegmentSetup
diff --git a/src/argon2.rs b/src/argon2.rs
new file mode 100644
index 000000000..e5a2c1071
--- /dev/null
+++ b/src/argon2.rs
@@ -0,0 +1,168 @@
+//! Argon2 version 1.3 (RFC 9106).
+//!
+//! The complete derivation is verified assembly (`VG.Spec.Argon2.deriveContract`),
+//! including H₀, memory initialization, every filling pass and final H′. Hashing
+//! follows the selected BLAKE2b backend. Rust only validates arguments and
+//! allocates the matrix and scratch. Lanes are evaluated serially: `threads`
+//! limits workers without changing the result.
+//!
+//! Argon2i leaks no input contents. Argon2d and Argon2id permit the
+//! data-dependent reference indices specified by `VG.Spec.Argon2.references`.
+
+#![cfg(all(target_arch = "x86_64", feature = "alloc"))]
+
+use alloc::vec::Vec;
+use core::fmt;
+
+use crate::arch::argon2::vg_argon2;
+use crate::hashes::blake2b::Blake2bBackend;
+
+/// Argon2's addressing variant.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum Variant {
+ /// Data-dependent addressing.
+ Argon2d = 0,
+ /// Data-independent addressing.
+ Argon2i = 1,
+ /// Independent addressing for the first half of the first pass.
+ Argon2id = 2,
+}
+
+/// Why [`derive`] refused to derive a key.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Error {
+ /// Invalid costs or lengths: iterations must be positive; lanes and
+ /// threads must be in 1..2²⁴; memory must be at least eight KiB per lane;
+ /// inputs must be shorter than 2³² bytes and output must be 4..2³² bytes.
+ InvalidParameters,
+ /// Allocating the memory matrix or scratch failed.
+ AllocationFailed,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(match self {
+ Self::InvalidParameters => "invalid Argon2 parameters",
+ Self::AllocationFailed => "could not allocate Argon2's memory",
+ })
+ }
+}
+
+impl core::error::Error for Error {}
+
+/// Fills `out` with an Argon2 version 1.3 key. `memory_cost` is in KiB and
+/// `iterations` counts passes. `lanes` is the algorithm's parallelism input;
+/// `threads` is a maximum worker count and does not change the key. `secret`
+/// and `associated_data` may be empty. The matrix contains
+/// `4 * lanes * floor(memory_cost / (4 * lanes))` blocks of 1024 bytes;
+/// scratch occupies another 16 KiB.
+///
+/// # Errors
+///
+/// Returns [`Error::InvalidParameters`] for invalid costs or lengths and
+/// [`Error::AllocationFailed`] if memory allocation fails. `out` is unchanged
+/// on either error.
+#[allow(clippy::too_many_arguments)]
+pub fn derive(
+ variant: Variant,
+ password: &[u8],
+ salt: &[u8],
+ iterations: u32,
+ memory_cost: u32,
+ lanes: u32,
+ threads: u32,
+ secret: &[u8],
+ associated_data: &[u8],
+ out: &mut [u8],
+) -> Result<(), Error> {
+ if !valid(
+ iterations,
+ memory_cost,
+ lanes,
+ threads,
+ [
+ password.len(),
+ salt.len(),
+ secret.len(),
+ associated_data.len(),
+ out.len(),
+ ],
+ ) {
+ return Err(Error::InvalidParameters);
+ }
+ let divisor = 4 * lanes;
+ let blocks = (memory_cost / divisor * divisor) as usize;
+ let mut matrix = allocate::<128>(blocks)?;
+ let mut scratch = allocate::<2048>(1)?;
+ let derive = match Blake2bBackend::select(crate::cpu::detected()) {
+ Blake2bBackend::Scalar => vg_argon2,
+ };
+ // SAFETY: validation establishes every numeric precondition of
+ // `deriveContract`. The matrix contains exactly the rounded block count
+ // and scratch is 2048 u64s. Input slices are valid for their lengths;
+ // output and both allocations are distinct mutable objects. They do not
+ // overlap each other, any input, the caller's stack arguments or the
+ // 344-byte assembly stack frame, and no region wraps the address space.
+ // The selected BLAKE2b backend supplies every required CPU feature.
+ unsafe {
+ derive(
+ variant as u32,
+ password.as_ptr(),
+ password.len(),
+ salt.as_ptr(),
+ salt.len(),
+ iterations,
+ memory_cost,
+ lanes,
+ threads,
+ secret.as_ptr(),
+ secret.len(),
+ associated_data.as_ptr(),
+ associated_data.len(),
+ matrix.as_mut_ptr(),
+ blocks,
+ scratch.as_mut_ptr(),
+ out.as_mut_ptr(),
+ out.len(),
+ )
+ };
+ Ok(())
+}
+
+fn valid(iterations: u32, memory: u32, lanes: u32, threads: u32, lengths: [usize; 5]) -> bool {
+ iterations > 0
+ && (1..1 << 24).contains(&lanes)
+ && (1..1 << 24).contains(&threads)
+ && memory >= 8 * lanes
+ && lengths[4] >= 4
+ && lengths.into_iter().all(|n| n <= u32::MAX as usize)
+}
+
+fn allocate(len: usize) -> Result, Error> {
+ let mut v = Vec::new();
+ v.try_reserve_exact(len)
+ .map_err(|_| Error::AllocationFailed)?;
+ v.resize(len, [0; N]);
+ Ok(v)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn length_limits() {
+ assert!(valid(1, 8, 1, 1, [u32::MAX as usize; 5]));
+ for j in 0..5 {
+ let mut lengths = [0, 0, 0, 0, 4];
+ lengths[j] = u32::MAX as usize + 1;
+ assert!(!valid(1, 8, 1, 1, lengths));
+ }
+ }
+
+ #[test]
+ fn allocation_failure() {
+ assert_eq!(allocate::<128>(usize::MAX), Err(Error::AllocationFailed));
+ }
+}
diff --git a/src/asm/x86_64/argon2.rs b/src/asm/x86_64/argon2.rs
index 6a7e78710..d4488678a 100644
--- a/src/asm/x86_64/argon2.rs
+++ b/src/asm/x86_64/argon2.rs
@@ -6203,3 +6203,2857 @@ pub(crate) unsafe extern "sysv64" fn vg_argon2_hprime(input: *const u8, input_le
vg_blake2b_finalize = sym super::blake2b::vg_blake2b_finalize,
)
}
+
+/// Argon2 version 1.3 (RFC 9106): derives `out_len` bytes at `out` from the password, salt, optional secret and associated data. `kind` selects Argon2d (0), Argon2i (1) or Argon2id (2). Performs the entire derivation: H₀, H′, initialization, all memory-filling passes, the final lane XOR and H′ of that block.
+///
+/// `iterations` is the pass count, `memory_cost` is the requested KiB count, and `lanes` is the algorithm's parallelism parameter. `threads` limits execution workers and does not change the result; serial execution is permitted.
+///
+/// Contract: `VG.Spec.Argon2.deriveContract`. Argon2i is constant time in all input contents. Argon2d/id additionally permit timing to depend on the sequence of data-dependent reference block indices (`VG.Spec.Argon2.references`), as required by their addressing rules, but on nothing else secret. Pointers, lengths and numeric parameters are public.
+///
+/// Serial lane evaluation honors every positive worker limit. All hashing uses the selected BLAKE2b streaming backend, including H₀ and every H′ call.
+///
+/// # Safety
+///
+/// * `password` must be valid for reads of `password_len` bytes.
+/// * `salt` must be valid for reads of `salt_len` bytes.
+/// * `secret` must be valid for reads of `secret_len` bytes.
+/// * `associated_data` must be valid for reads of `ad_len` bytes.
+/// * `memory` must be valid for reads and writes of `1024 * blocks` bytes.
+/// * `scratch` must be valid for reads and writes of 16384 bytes.
+/// * `out` must be valid for reads and writes of `out_len` bytes.
+/// * `kind` must be 0, 1 or 2; `iterations` must be positive; `lanes` and `threads` must be in 1..2^24-1; `memory_cost` must be at least `8 * lanes`.
+/// * All input lengths must be less than 2^32; `out_len` must be in 4..2^32-1.
+/// * `blocks` must equal `4 * lanes * floor(memory_cost / (4 * lanes))`.
+/// * `memory` and `scratch` are working space: their initial contents are arbitrary and their contents on return are unspecified.
+/// * `memory`, `scratch` and `out` must not overlap each other, `password`, `salt`, `secret`, `associated_data` or the arguments on the stack (distinct Rust objects never do).
+/// * None of `password`, `salt`, `secret`, `associated_data`, `memory`, `scratch` and `out` may overlap the return address on the stack or the 344 bytes of stack below it, or wrap around the end of the address space (no Rust object does).
+#[unsafe(naked)]
+pub(crate) unsafe extern "sysv64" fn vg_argon2(kind: u32, password: *const u8, password_len: usize, salt: *const u8, salt_len: usize, iterations: u32, memory_cost: u32, lanes: u32, threads: u32, secret: *const u8, secret_len: usize, associated_data: *const u8, ad_len: usize, memory: *mut [u64; 128], blocks: usize, scratch: *mut [u64; 2048], out: *mut u8, out_len: usize) {
+ core::arch::naked_asm!(
+ "push rbx",
+ "push rbp",
+ "push r12",
+ "push r13",
+ "push r14",
+ "push r15",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "push rax",
+ "mov rax, QWORD PTR [rsp+328]",
+ "mov QWORD PTR [rsp+176], rax",
+ "mov rax, QWORD PTR [rsp+336]",
+ "mov QWORD PTR [rsp+184], rax",
+ "mov rax, QWORD PTR [rsp+344]",
+ "mov QWORD PTR [rsp+192], rax",
+ "mov rax, QWORD PTR [rsp+352]",
+ "mov QWORD PTR [rsp+200], rax",
+ "mov rax, QWORD PTR [rsp+360]",
+ "mov QWORD PTR [rsp+208], rax",
+ "mov rax, QWORD PTR [rsp+368]",
+ "mov QWORD PTR [rsp+216], rax",
+ "mov rax, QWORD PTR [rsp+376]",
+ "mov QWORD PTR [rsp+224], rax",
+ "mov rax, QWORD PTR [rsp+384]",
+ "mov QWORD PTR [rsp+232], rax",
+ "mov rax, QWORD PTR [rsp+392]",
+ "mov QWORD PTR [rsp+240], rax",
+ "mov rax, QWORD PTR [rsp+400]",
+ "mov QWORD PTR [rsp+248], rax",
+ "mov rax, QWORD PTR [rsp+408]",
+ "mov QWORD PTR [rsp+256], rax",
+ "mov rax, QWORD PTR [rsp+416]",
+ "mov QWORD PTR [rsp+264], rax",
+ "mov rbp, rsp",
+ "mov edi, edi",
+ "mov r9d, r9d",
+ "mov QWORD PTR [rbp+72], r9",
+ "mov QWORD PTR [rbp+80], r8",
+ "mov QWORD PTR [rbp+88], rcx",
+ "mov QWORD PTR [rbp+96], rdx",
+ "mov QWORD PTR [rbp+104], rsi",
+ "mov QWORD PTR [rbp+112], rdi",
+ "mov rbx, QWORD PTR [rbp+248]",
+ "mov rax, QWORD PTR [rbp+176]",
+ "mov eax, eax",
+ "mov QWORD PTR [rbp+176], rax",
+ "mov rax, QWORD PTR [rbp+184]",
+ "mov eax, eax",
+ "mov QWORD PTR [rbp+184], rax",
+ "mov rax, QWORD PTR [rbp+192]",
+ "mov eax, eax",
+ "mov QWORD PTR [rbp+192], rax",
+ "mov rdi, QWORD PTR [rbp+176]",
+ "mov rsi, QWORD PTR [rbp+184]",
+ "add rsi, rsi",
+ "add rsi, rsi",
+ "mov r8d, 0",
+ "mov r9d, 0",
+ "mov eax, 0",
+ "cmp rax, 0",
+ "mov rcx, rdi",
+ "shr rcx, 32",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 31",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 30",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 29",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 28",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 27",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 26",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 25",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 24",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 23",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 22",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 21",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 20",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 19",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 18",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 17",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 16",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 15",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 14",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 13",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 12",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 11",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 10",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 9",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 8",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 7",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 6",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 5",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 4",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 3",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 2",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 1",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov r13, r9",
+ "add r13, r13",
+ "add r13, r13",
+ "mov esi, 64",
+ "mov rdi, rbx",
+ "mov rdx, rbx",
+ "add rdx, 832",
+ "mov ecx, 0",
+ "call {vg_blake2b_init}",
+ "mov rax, QWORD PTR [rbp+184]",
+ "mov DWORD PTR [rbx+768], eax",
+ "mov rax, QWORD PTR [rbp+264]",
+ "mov DWORD PTR [rbx+772], eax",
+ "mov rax, QWORD PTR [rbp+176]",
+ "mov DWORD PTR [rbx+776], eax",
+ "mov rax, QWORD PTR [rbp+72]",
+ "mov DWORD PTR [rbx+780], eax",
+ "mov eax, 19",
+ "mov DWORD PTR [rbx+784], eax",
+ "mov rax, QWORD PTR [rbp+112]",
+ "mov DWORD PTR [rbx+788], eax",
+ "mov esi, 0",
+ "mov rdx, rbx",
+ "add rdx, 768",
+ "mov ecx, 24",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "mov r12d, 24",
+ "mov r14, QWORD PTR [rbp+96]",
+ "mov DWORD PTR [rbx+792], r14d",
+ "mov rsi, r12",
+ "mov rdx, rbx",
+ "add rdx, 792",
+ "mov ecx, 4",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, 4",
+ "mov rsi, r12",
+ "mov rdx, QWORD PTR [rbp+104]",
+ "mov rcx, r14",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, r14",
+ "mov r14, QWORD PTR [rbp+80]",
+ "mov DWORD PTR [rbx+792], r14d",
+ "mov rsi, r12",
+ "mov rdx, rbx",
+ "add rdx, 792",
+ "mov ecx, 4",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, 4",
+ "mov rsi, r12",
+ "mov rdx, QWORD PTR [rbp+88]",
+ "mov rcx, r14",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, r14",
+ "mov r14, QWORD PTR [rbp+208]",
+ "mov DWORD PTR [rbx+792], r14d",
+ "mov rsi, r12",
+ "mov rdx, rbx",
+ "add rdx, 792",
+ "mov ecx, 4",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, 4",
+ "mov rsi, r12",
+ "mov rdx, QWORD PTR [rbp+200]",
+ "mov rcx, r14",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, r14",
+ "mov r14, QWORD PTR [rbp+224]",
+ "mov DWORD PTR [rbx+792], r14d",
+ "mov rsi, r12",
+ "mov rdx, rbx",
+ "add rdx, 792",
+ "mov ecx, 4",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, 4",
+ "mov rsi, r12",
+ "mov rdx, QWORD PTR [rbp+216]",
+ "mov rcx, r14",
+ "mov rdi, rbx",
+ "mov r8, rbx",
+ "add r8, 192",
+ "call {vg_blake2b_update}",
+ "add r12, r14",
+ "mov rsi, r12",
+ "mov rdi, rbx",
+ "mov rdx, rbx",
+ "add rdx, 768",
+ "mov rcx, rbx",
+ "add rcx, 192",
+ "call {vg_blake2b_finalize}",
+ "mov r14, rbp",
+ "mov eax, 64",
+ "mov rdx, rbx",
+ "add rdx, 768",
+ "20:",
+ "movzx ecx, BYTE PTR [rdx]",
+ "mov BYTE PTR [r14], cl",
+ "add rdx, 1",
+ "add r14, 1",
+ "sub rax, 1",
+ "jne 20b",
+ "mov r14, QWORD PTR [rbp+232]",
+ "mov rax, QWORD PTR [rbp+240]",
+ "mov ecx, 0",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "21:",
+ "mov QWORD PTR [r14], rcx",
+ "add r14, 8",
+ "sub rax, 1",
+ "jne 21b",
+ "mov r14, QWORD PTR [rbp+232]",
+ "mov r12d, 0",
+ "mov r15, QWORD PTR [rbp+184]",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "add r13, r13",
+ "22:",
+ "mov eax, 0",
+ "mov DWORD PTR [rbp+64], eax",
+ "mov DWORD PTR [rbp+68], r12d",
+ "mov rdi, rbp",
+ "mov esi, 72",
+ "mov rdx, r14",
+ "mov ecx, 1024",
+ "mov r8, rbx",
+ "call {vg_argon2_hprime}",
+ "add r14, 1024",
+ "mov eax, 1",
+ "mov DWORD PTR [rbp+64], eax",
+ "mov DWORD PTR [rbp+68], r12d",
+ "mov rdi, rbp",
+ "mov esi, 72",
+ "mov rdx, r14",
+ "mov ecx, 1024",
+ "mov r8, rbx",
+ "call {vg_argon2_hprime}",
+ "add r14, r13",
+ "sub r14, 1024",
+ "add r12, 1",
+ "sub r15, 1",
+ "jne 22b",
+ "mov r12, r13",
+ "shr r12, 10",
+ "shr r13, 12",
+ "mov rax, 0",
+ "mov QWORD PTR [rbp], rax",
+ "mov rbx, 0",
+ "mov r14, 0",
+ "23:",
+ "mov r14, 0",
+ "24:",
+ "mov rbx, 0",
+ "25:",
+ "mov rax, 0",
+ "mov QWORD PTR [rbp+8], rax",
+ "mov rcx, QWORD PTR [rbp]",
+ "or rcx, r14",
+ "cmp rcx, 0",
+ "je 26f",
+ "mov r15, 0",
+ "jmp 27f",
+ "26:",
+ "mov r15, 2",
+ "27:",
+ "cmp r15, r13",
+ "jb 28f",
+ "jmp 29f",
+ "28:",
+ "210:",
+ "mov rax, QWORD PTR [rbp+112]",
+ "mov r10, rax",
+ "xor r10, 1",
+ "cmp r10, 1",
+ "sbb r10, r10",
+ "mov r8, rax",
+ "xor r8, 2",
+ "cmp r8, 1",
+ "sbb r8, r8",
+ "mov r9, QWORD PTR [rbp]",
+ "cmp r9, 1",
+ "sbb r9, r9",
+ "cmp r14, 2",
+ "sbb r11, r11",
+ "and r8, r9",
+ "and r8, r11",
+ "or r10, r8",
+ "and r10, 1",
+ "cmp r10, 0",
+ "je 211f",
+ "mov rax, r15",
+ "shr rax, 7",
+ "add rax, 1",
+ "cmp rax, QWORD PTR [rbp+8]",
+ "je 213f",
+ "mov QWORD PTR [rbp+8], rax",
+ "mov rdi, QWORD PTR [rbp+248]",
+ "add rdi, 5120",
+ "mov rax, 0",
+ "mov QWORD PTR [rdi], rax",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov QWORD PTR [rdi+1016], rax",
+ "mov rdi, QWORD PTR [rbp+248]",
+ "add rdi, 7168",
+ "mov rax, 0",
+ "mov QWORD PTR [rdi], rax",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov QWORD PTR [rdi+1016], rax",
+ "mov rdi, QWORD PTR [rbp+248]",
+ "add rdi, 5120",
+ "mov rax, QWORD PTR [rbp]",
+ "mov QWORD PTR [rdi], rax",
+ "mov QWORD PTR [rdi+8], rbx",
+ "mov QWORD PTR [rdi+16], r14",
+ "mov rax, QWORD PTR [rbp+240]",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov rax, QWORD PTR [rbp+72]",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov rax, QWORD PTR [rbp+112]",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov rax, QWORD PTR [rbp+8]",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov rcx, QWORD PTR [rbp+248]",
+ "mov rdi, rcx",
+ "add rdi, 7168",
+ "mov rsi, rcx",
+ "add rsi, 5120",
+ "mov rdx, rcx",
+ "add rdx, 4096",
+ "call {vg_argon2_compress}",
+ "mov rcx, QWORD PTR [rbp+248]",
+ "mov rdi, rcx",
+ "add rdi, 7168",
+ "mov rsi, rcx",
+ "add rsi, 4096",
+ "mov rdx, rcx",
+ "add rdx, 6144",
+ "call {vg_argon2_compress}",
+ "jmp 214f",
+ "213:",
+ "214:",
+ "mov rcx, QWORD PTR [rbp+248]",
+ "mov rax, r15",
+ "and rax, 127",
+ "mov rdi, QWORD PTR [rcx+rax*8+6144]",
+ "jmp 212f",
+ "211:",
+ "mov r8, QWORD PTR [rbp+232]",
+ "mov rax, r14",
+ "mul r13",
+ "mov rcx, rax",
+ "add rcx, r15",
+ "cmp rcx, 0",
+ "je 215f",
+ "mov rdi, rcx",
+ "jmp 216f",
+ "215:",
+ "mov rdi, r12",
+ "216:",
+ "sub rdi, 1",
+ "mov rcx, rdi",
+ "mov rax, rbx",
+ "mul r12",
+ "add rax, rcx",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, r8",
+ "mov rdi, QWORD PTR [rax]",
+ "212:",
+ "mov rsi, QWORD PTR [rbp+184]",
+ "mov r11, rdi",
+ "shr rdi, 32",
+ "mov r8d, 0",
+ "mov r9d, 0",
+ "mov eax, 0",
+ "cmp rax, 0",
+ "mov rcx, rdi",
+ "shr rcx, 32",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 31",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 30",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 29",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 28",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 27",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 26",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 25",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 24",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 23",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 22",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 21",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 20",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 19",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 18",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 17",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 16",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 15",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 14",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 13",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 12",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 11",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 10",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 9",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 8",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 7",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 6",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 5",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 4",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 3",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 2",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov rcx, rdi",
+ "shr rcx, 1",
+ "adc r8, r8",
+ "mov r10, r8",
+ "sub r8, rsi",
+ "sbb rax, rax",
+ "xor r10, r8",
+ "and r10, rax",
+ "xor r8, r10",
+ "add rax, 1",
+ "add r9, r9",
+ "add r9, rax",
+ "mov r9, QWORD PTR [rbp]",
+ "mov rax, r9",
+ "or rax, r14",
+ "je 217f",
+ "jmp 218f",
+ "217:",
+ "mov r8, rbx",
+ "218:",
+ "mov rdi, r8",
+ "mov rsi, rbx",
+ "cmp r9, 0",
+ "je 219f",
+ "mov rax, r14",
+ "add rax, 1",
+ "mul r13",
+ "mov r10, rax",
+ "cmp r14, 3",
+ "je 221f",
+ "jmp 222f",
+ "221:",
+ "mov r10, 0",
+ "222:",
+ "jmp 220f",
+ "219:",
+ "mov r10, 0",
+ "220:",
+ "cmp r9, 0",
+ "je 223f",
+ "mov rax, r12",
+ "sub rax, r13",
+ "mov rcx, rax",
+ "mov rdx, rax",
+ "add rdx, r15",
+ "sub rdx, 1",
+ "jmp 224f",
+ "223:",
+ "mov rax, r13",
+ "mul r14",
+ "mov rcx, rax",
+ "mov rdx, rax",
+ "add rdx, r15",
+ "sub rdx, 1",
+ "224:",
+ "mov r8, r15",
+ "sub r8, 1",
+ "sbb r9, r9",
+ "add rcx, r9",
+ "mov rax, rdi",
+ "xor rax, rsi",
+ "sub rax, 1",
+ "sbb rax, rax",
+ "mov r8, rcx",
+ "xor rdx, rcx",
+ "and rdx, rax",
+ "xor r8, rdx",
+ "mov r9, rdi",
+ "mov rdi, r11",
+ "mov rsi, r8",
+ "mov eax, edi",
+ "mul rax",
+ "shr rax, 32",
+ "mul rsi",
+ "shr rax, 32",
+ "mov rcx, rsi",
+ "sub rcx, 1",
+ "sub rcx, rax",
+ "mov rax, rcx",
+ "mov rdi, rax",
+ "add rdi, r10",
+ "mov rsi, r12",
+ "mov r10, rdi",
+ "sub rdi, rsi",
+ "sbb rax, rax",
+ "xor r10, rdi",
+ "and r10, rax",
+ "xor rdi, r10",
+ "mov r8, QWORD PTR [rbp+232]",
+ "mov rsi, rdi",
+ "mov rax, r14",
+ "mul r13",
+ "mov rcx, rax",
+ "add rcx, r15",
+ "cmp rcx, 0",
+ "je 225f",
+ "mov rdi, rcx",
+ "jmp 226f",
+ "225:",
+ "mov rdi, r12",
+ "226:",
+ "sub rdi, 1",
+ "mov rax, rbx",
+ "mul r12",
+ "add rax, rcx",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, r8",
+ "mov r10, rax",
+ "mov rcx, rdi",
+ "mov rax, rbx",
+ "mul r12",
+ "add rax, rcx",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, r8",
+ "mov r11, rax",
+ "mov rcx, rsi",
+ "mov rax, r9",
+ "mul r12",
+ "add rax, rcx",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, r8",
+ "mov rsi, rax",
+ "mov rdi, r11",
+ "mov QWORD PTR [rbp+16], r10",
+ "mov rcx, QWORD PTR [rbp+248]",
+ "mov rdx, rcx",
+ "add rdx, 4096",
+ "call {vg_argon2_compress}",
+ "mov rdi, QWORD PTR [rbp+16]",
+ "mov rsi, QWORD PTR [rbp+248]",
+ "add rsi, 4096",
+ "mov r9, QWORD PTR [rbp]",
+ "cmp r9, 0",
+ "je 227f",
+ "mov rax, QWORD PTR [rsi]",
+ "xor rax, QWORD PTR [rdi]",
+ "mov QWORD PTR [rdi], rax",
+ "mov rax, QWORD PTR [rsi+8]",
+ "xor rax, QWORD PTR [rdi+8]",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov rax, QWORD PTR [rsi+16]",
+ "xor rax, QWORD PTR [rdi+16]",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov rax, QWORD PTR [rsi+24]",
+ "xor rax, QWORD PTR [rdi+24]",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov rax, QWORD PTR [rsi+32]",
+ "xor rax, QWORD PTR [rdi+32]",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov rax, QWORD PTR [rsi+40]",
+ "xor rax, QWORD PTR [rdi+40]",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov rax, QWORD PTR [rsi+48]",
+ "xor rax, QWORD PTR [rdi+48]",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov rax, QWORD PTR [rsi+56]",
+ "xor rax, QWORD PTR [rdi+56]",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov rax, QWORD PTR [rsi+64]",
+ "xor rax, QWORD PTR [rdi+64]",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov rax, QWORD PTR [rsi+72]",
+ "xor rax, QWORD PTR [rdi+72]",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov rax, QWORD PTR [rsi+80]",
+ "xor rax, QWORD PTR [rdi+80]",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov rax, QWORD PTR [rsi+88]",
+ "xor rax, QWORD PTR [rdi+88]",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov rax, QWORD PTR [rsi+96]",
+ "xor rax, QWORD PTR [rdi+96]",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov rax, QWORD PTR [rsi+104]",
+ "xor rax, QWORD PTR [rdi+104]",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov rax, QWORD PTR [rsi+112]",
+ "xor rax, QWORD PTR [rdi+112]",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov rax, QWORD PTR [rsi+120]",
+ "xor rax, QWORD PTR [rdi+120]",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov rax, QWORD PTR [rsi+128]",
+ "xor rax, QWORD PTR [rdi+128]",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov rax, QWORD PTR [rsi+136]",
+ "xor rax, QWORD PTR [rdi+136]",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov rax, QWORD PTR [rsi+144]",
+ "xor rax, QWORD PTR [rdi+144]",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov rax, QWORD PTR [rsi+152]",
+ "xor rax, QWORD PTR [rdi+152]",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov rax, QWORD PTR [rsi+160]",
+ "xor rax, QWORD PTR [rdi+160]",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov rax, QWORD PTR [rsi+168]",
+ "xor rax, QWORD PTR [rdi+168]",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov rax, QWORD PTR [rsi+176]",
+ "xor rax, QWORD PTR [rdi+176]",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov rax, QWORD PTR [rsi+184]",
+ "xor rax, QWORD PTR [rdi+184]",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov rax, QWORD PTR [rsi+192]",
+ "xor rax, QWORD PTR [rdi+192]",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov rax, QWORD PTR [rsi+200]",
+ "xor rax, QWORD PTR [rdi+200]",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov rax, QWORD PTR [rsi+208]",
+ "xor rax, QWORD PTR [rdi+208]",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov rax, QWORD PTR [rsi+216]",
+ "xor rax, QWORD PTR [rdi+216]",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov rax, QWORD PTR [rsi+224]",
+ "xor rax, QWORD PTR [rdi+224]",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov rax, QWORD PTR [rsi+232]",
+ "xor rax, QWORD PTR [rdi+232]",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov rax, QWORD PTR [rsi+240]",
+ "xor rax, QWORD PTR [rdi+240]",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov rax, QWORD PTR [rsi+248]",
+ "xor rax, QWORD PTR [rdi+248]",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov rax, QWORD PTR [rsi+256]",
+ "xor rax, QWORD PTR [rdi+256]",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov rax, QWORD PTR [rsi+264]",
+ "xor rax, QWORD PTR [rdi+264]",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov rax, QWORD PTR [rsi+272]",
+ "xor rax, QWORD PTR [rdi+272]",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov rax, QWORD PTR [rsi+280]",
+ "xor rax, QWORD PTR [rdi+280]",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov rax, QWORD PTR [rsi+288]",
+ "xor rax, QWORD PTR [rdi+288]",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov rax, QWORD PTR [rsi+296]",
+ "xor rax, QWORD PTR [rdi+296]",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov rax, QWORD PTR [rsi+304]",
+ "xor rax, QWORD PTR [rdi+304]",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov rax, QWORD PTR [rsi+312]",
+ "xor rax, QWORD PTR [rdi+312]",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov rax, QWORD PTR [rsi+320]",
+ "xor rax, QWORD PTR [rdi+320]",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov rax, QWORD PTR [rsi+328]",
+ "xor rax, QWORD PTR [rdi+328]",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov rax, QWORD PTR [rsi+336]",
+ "xor rax, QWORD PTR [rdi+336]",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov rax, QWORD PTR [rsi+344]",
+ "xor rax, QWORD PTR [rdi+344]",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov rax, QWORD PTR [rsi+352]",
+ "xor rax, QWORD PTR [rdi+352]",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov rax, QWORD PTR [rsi+360]",
+ "xor rax, QWORD PTR [rdi+360]",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov rax, QWORD PTR [rsi+368]",
+ "xor rax, QWORD PTR [rdi+368]",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov rax, QWORD PTR [rsi+376]",
+ "xor rax, QWORD PTR [rdi+376]",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov rax, QWORD PTR [rsi+384]",
+ "xor rax, QWORD PTR [rdi+384]",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov rax, QWORD PTR [rsi+392]",
+ "xor rax, QWORD PTR [rdi+392]",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov rax, QWORD PTR [rsi+400]",
+ "xor rax, QWORD PTR [rdi+400]",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov rax, QWORD PTR [rsi+408]",
+ "xor rax, QWORD PTR [rdi+408]",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov rax, QWORD PTR [rsi+416]",
+ "xor rax, QWORD PTR [rdi+416]",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov rax, QWORD PTR [rsi+424]",
+ "xor rax, QWORD PTR [rdi+424]",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov rax, QWORD PTR [rsi+432]",
+ "xor rax, QWORD PTR [rdi+432]",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov rax, QWORD PTR [rsi+440]",
+ "xor rax, QWORD PTR [rdi+440]",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov rax, QWORD PTR [rsi+448]",
+ "xor rax, QWORD PTR [rdi+448]",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov rax, QWORD PTR [rsi+456]",
+ "xor rax, QWORD PTR [rdi+456]",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov rax, QWORD PTR [rsi+464]",
+ "xor rax, QWORD PTR [rdi+464]",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov rax, QWORD PTR [rsi+472]",
+ "xor rax, QWORD PTR [rdi+472]",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov rax, QWORD PTR [rsi+480]",
+ "xor rax, QWORD PTR [rdi+480]",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov rax, QWORD PTR [rsi+488]",
+ "xor rax, QWORD PTR [rdi+488]",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov rax, QWORD PTR [rsi+496]",
+ "xor rax, QWORD PTR [rdi+496]",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov rax, QWORD PTR [rsi+504]",
+ "xor rax, QWORD PTR [rdi+504]",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov rax, QWORD PTR [rsi+512]",
+ "xor rax, QWORD PTR [rdi+512]",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov rax, QWORD PTR [rsi+520]",
+ "xor rax, QWORD PTR [rdi+520]",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov rax, QWORD PTR [rsi+528]",
+ "xor rax, QWORD PTR [rdi+528]",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov rax, QWORD PTR [rsi+536]",
+ "xor rax, QWORD PTR [rdi+536]",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov rax, QWORD PTR [rsi+544]",
+ "xor rax, QWORD PTR [rdi+544]",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov rax, QWORD PTR [rsi+552]",
+ "xor rax, QWORD PTR [rdi+552]",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov rax, QWORD PTR [rsi+560]",
+ "xor rax, QWORD PTR [rdi+560]",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov rax, QWORD PTR [rsi+568]",
+ "xor rax, QWORD PTR [rdi+568]",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov rax, QWORD PTR [rsi+576]",
+ "xor rax, QWORD PTR [rdi+576]",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov rax, QWORD PTR [rsi+584]",
+ "xor rax, QWORD PTR [rdi+584]",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov rax, QWORD PTR [rsi+592]",
+ "xor rax, QWORD PTR [rdi+592]",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov rax, QWORD PTR [rsi+600]",
+ "xor rax, QWORD PTR [rdi+600]",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov rax, QWORD PTR [rsi+608]",
+ "xor rax, QWORD PTR [rdi+608]",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov rax, QWORD PTR [rsi+616]",
+ "xor rax, QWORD PTR [rdi+616]",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov rax, QWORD PTR [rsi+624]",
+ "xor rax, QWORD PTR [rdi+624]",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov rax, QWORD PTR [rsi+632]",
+ "xor rax, QWORD PTR [rdi+632]",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov rax, QWORD PTR [rsi+640]",
+ "xor rax, QWORD PTR [rdi+640]",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov rax, QWORD PTR [rsi+648]",
+ "xor rax, QWORD PTR [rdi+648]",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov rax, QWORD PTR [rsi+656]",
+ "xor rax, QWORD PTR [rdi+656]",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov rax, QWORD PTR [rsi+664]",
+ "xor rax, QWORD PTR [rdi+664]",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov rax, QWORD PTR [rsi+672]",
+ "xor rax, QWORD PTR [rdi+672]",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov rax, QWORD PTR [rsi+680]",
+ "xor rax, QWORD PTR [rdi+680]",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov rax, QWORD PTR [rsi+688]",
+ "xor rax, QWORD PTR [rdi+688]",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov rax, QWORD PTR [rsi+696]",
+ "xor rax, QWORD PTR [rdi+696]",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov rax, QWORD PTR [rsi+704]",
+ "xor rax, QWORD PTR [rdi+704]",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov rax, QWORD PTR [rsi+712]",
+ "xor rax, QWORD PTR [rdi+712]",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov rax, QWORD PTR [rsi+720]",
+ "xor rax, QWORD PTR [rdi+720]",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov rax, QWORD PTR [rsi+728]",
+ "xor rax, QWORD PTR [rdi+728]",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov rax, QWORD PTR [rsi+736]",
+ "xor rax, QWORD PTR [rdi+736]",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov rax, QWORD PTR [rsi+744]",
+ "xor rax, QWORD PTR [rdi+744]",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov rax, QWORD PTR [rsi+752]",
+ "xor rax, QWORD PTR [rdi+752]",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov rax, QWORD PTR [rsi+760]",
+ "xor rax, QWORD PTR [rdi+760]",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov rax, QWORD PTR [rsi+768]",
+ "xor rax, QWORD PTR [rdi+768]",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov rax, QWORD PTR [rsi+776]",
+ "xor rax, QWORD PTR [rdi+776]",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov rax, QWORD PTR [rsi+784]",
+ "xor rax, QWORD PTR [rdi+784]",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov rax, QWORD PTR [rsi+792]",
+ "xor rax, QWORD PTR [rdi+792]",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov rax, QWORD PTR [rsi+800]",
+ "xor rax, QWORD PTR [rdi+800]",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov rax, QWORD PTR [rsi+808]",
+ "xor rax, QWORD PTR [rdi+808]",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov rax, QWORD PTR [rsi+816]",
+ "xor rax, QWORD PTR [rdi+816]",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov rax, QWORD PTR [rsi+824]",
+ "xor rax, QWORD PTR [rdi+824]",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov rax, QWORD PTR [rsi+832]",
+ "xor rax, QWORD PTR [rdi+832]",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov rax, QWORD PTR [rsi+840]",
+ "xor rax, QWORD PTR [rdi+840]",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov rax, QWORD PTR [rsi+848]",
+ "xor rax, QWORD PTR [rdi+848]",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov rax, QWORD PTR [rsi+856]",
+ "xor rax, QWORD PTR [rdi+856]",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov rax, QWORD PTR [rsi+864]",
+ "xor rax, QWORD PTR [rdi+864]",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov rax, QWORD PTR [rsi+872]",
+ "xor rax, QWORD PTR [rdi+872]",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov rax, QWORD PTR [rsi+880]",
+ "xor rax, QWORD PTR [rdi+880]",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov rax, QWORD PTR [rsi+888]",
+ "xor rax, QWORD PTR [rdi+888]",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov rax, QWORD PTR [rsi+896]",
+ "xor rax, QWORD PTR [rdi+896]",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov rax, QWORD PTR [rsi+904]",
+ "xor rax, QWORD PTR [rdi+904]",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov rax, QWORD PTR [rsi+912]",
+ "xor rax, QWORD PTR [rdi+912]",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov rax, QWORD PTR [rsi+920]",
+ "xor rax, QWORD PTR [rdi+920]",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov rax, QWORD PTR [rsi+928]",
+ "xor rax, QWORD PTR [rdi+928]",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov rax, QWORD PTR [rsi+936]",
+ "xor rax, QWORD PTR [rdi+936]",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov rax, QWORD PTR [rsi+944]",
+ "xor rax, QWORD PTR [rdi+944]",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov rax, QWORD PTR [rsi+952]",
+ "xor rax, QWORD PTR [rdi+952]",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov rax, QWORD PTR [rsi+960]",
+ "xor rax, QWORD PTR [rdi+960]",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov rax, QWORD PTR [rsi+968]",
+ "xor rax, QWORD PTR [rdi+968]",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov rax, QWORD PTR [rsi+976]",
+ "xor rax, QWORD PTR [rdi+976]",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov rax, QWORD PTR [rsi+984]",
+ "xor rax, QWORD PTR [rdi+984]",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov rax, QWORD PTR [rsi+992]",
+ "xor rax, QWORD PTR [rdi+992]",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov rax, QWORD PTR [rsi+1000]",
+ "xor rax, QWORD PTR [rdi+1000]",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov rax, QWORD PTR [rsi+1008]",
+ "xor rax, QWORD PTR [rdi+1008]",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov rax, QWORD PTR [rsi+1016]",
+ "xor rax, QWORD PTR [rdi+1016]",
+ "mov QWORD PTR [rdi+1016], rax",
+ "jmp 228f",
+ "227:",
+ "mov rax, QWORD PTR [rsi]",
+ "mov QWORD PTR [rdi], rax",
+ "mov rax, QWORD PTR [rsi+8]",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov rax, QWORD PTR [rsi+16]",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov rax, QWORD PTR [rsi+24]",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov rax, QWORD PTR [rsi+32]",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov rax, QWORD PTR [rsi+40]",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov rax, QWORD PTR [rsi+48]",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov rax, QWORD PTR [rsi+56]",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov rax, QWORD PTR [rsi+64]",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov rax, QWORD PTR [rsi+72]",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov rax, QWORD PTR [rsi+80]",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov rax, QWORD PTR [rsi+88]",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov rax, QWORD PTR [rsi+96]",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov rax, QWORD PTR [rsi+104]",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov rax, QWORD PTR [rsi+112]",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov rax, QWORD PTR [rsi+120]",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov rax, QWORD PTR [rsi+128]",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov rax, QWORD PTR [rsi+136]",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov rax, QWORD PTR [rsi+144]",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov rax, QWORD PTR [rsi+152]",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov rax, QWORD PTR [rsi+160]",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov rax, QWORD PTR [rsi+168]",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov rax, QWORD PTR [rsi+176]",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov rax, QWORD PTR [rsi+184]",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov rax, QWORD PTR [rsi+192]",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov rax, QWORD PTR [rsi+200]",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov rax, QWORD PTR [rsi+208]",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov rax, QWORD PTR [rsi+216]",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov rax, QWORD PTR [rsi+224]",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov rax, QWORD PTR [rsi+232]",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov rax, QWORD PTR [rsi+240]",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov rax, QWORD PTR [rsi+248]",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov rax, QWORD PTR [rsi+256]",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov rax, QWORD PTR [rsi+264]",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov rax, QWORD PTR [rsi+272]",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov rax, QWORD PTR [rsi+280]",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov rax, QWORD PTR [rsi+288]",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov rax, QWORD PTR [rsi+296]",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov rax, QWORD PTR [rsi+304]",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov rax, QWORD PTR [rsi+312]",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov rax, QWORD PTR [rsi+320]",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov rax, QWORD PTR [rsi+328]",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov rax, QWORD PTR [rsi+336]",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov rax, QWORD PTR [rsi+344]",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov rax, QWORD PTR [rsi+352]",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov rax, QWORD PTR [rsi+360]",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov rax, QWORD PTR [rsi+368]",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov rax, QWORD PTR [rsi+376]",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov rax, QWORD PTR [rsi+384]",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov rax, QWORD PTR [rsi+392]",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov rax, QWORD PTR [rsi+400]",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov rax, QWORD PTR [rsi+408]",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov rax, QWORD PTR [rsi+416]",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov rax, QWORD PTR [rsi+424]",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov rax, QWORD PTR [rsi+432]",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov rax, QWORD PTR [rsi+440]",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov rax, QWORD PTR [rsi+448]",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov rax, QWORD PTR [rsi+456]",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov rax, QWORD PTR [rsi+464]",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov rax, QWORD PTR [rsi+472]",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov rax, QWORD PTR [rsi+480]",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov rax, QWORD PTR [rsi+488]",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov rax, QWORD PTR [rsi+496]",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov rax, QWORD PTR [rsi+504]",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov rax, QWORD PTR [rsi+512]",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov rax, QWORD PTR [rsi+520]",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov rax, QWORD PTR [rsi+528]",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov rax, QWORD PTR [rsi+536]",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov rax, QWORD PTR [rsi+544]",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov rax, QWORD PTR [rsi+552]",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov rax, QWORD PTR [rsi+560]",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov rax, QWORD PTR [rsi+568]",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov rax, QWORD PTR [rsi+576]",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov rax, QWORD PTR [rsi+584]",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov rax, QWORD PTR [rsi+592]",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov rax, QWORD PTR [rsi+600]",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov rax, QWORD PTR [rsi+608]",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov rax, QWORD PTR [rsi+616]",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov rax, QWORD PTR [rsi+624]",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov rax, QWORD PTR [rsi+632]",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov rax, QWORD PTR [rsi+640]",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov rax, QWORD PTR [rsi+648]",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov rax, QWORD PTR [rsi+656]",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov rax, QWORD PTR [rsi+664]",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov rax, QWORD PTR [rsi+672]",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov rax, QWORD PTR [rsi+680]",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov rax, QWORD PTR [rsi+688]",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov rax, QWORD PTR [rsi+696]",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov rax, QWORD PTR [rsi+704]",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov rax, QWORD PTR [rsi+712]",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov rax, QWORD PTR [rsi+720]",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov rax, QWORD PTR [rsi+728]",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov rax, QWORD PTR [rsi+736]",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov rax, QWORD PTR [rsi+744]",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov rax, QWORD PTR [rsi+752]",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov rax, QWORD PTR [rsi+760]",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov rax, QWORD PTR [rsi+768]",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov rax, QWORD PTR [rsi+776]",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov rax, QWORD PTR [rsi+784]",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov rax, QWORD PTR [rsi+792]",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov rax, QWORD PTR [rsi+800]",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov rax, QWORD PTR [rsi+808]",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov rax, QWORD PTR [rsi+816]",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov rax, QWORD PTR [rsi+824]",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov rax, QWORD PTR [rsi+832]",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov rax, QWORD PTR [rsi+840]",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov rax, QWORD PTR [rsi+848]",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov rax, QWORD PTR [rsi+856]",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov rax, QWORD PTR [rsi+864]",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov rax, QWORD PTR [rsi+872]",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov rax, QWORD PTR [rsi+880]",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov rax, QWORD PTR [rsi+888]",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov rax, QWORD PTR [rsi+896]",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov rax, QWORD PTR [rsi+904]",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov rax, QWORD PTR [rsi+912]",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov rax, QWORD PTR [rsi+920]",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov rax, QWORD PTR [rsi+928]",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov rax, QWORD PTR [rsi+936]",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov rax, QWORD PTR [rsi+944]",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov rax, QWORD PTR [rsi+952]",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov rax, QWORD PTR [rsi+960]",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov rax, QWORD PTR [rsi+968]",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov rax, QWORD PTR [rsi+976]",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov rax, QWORD PTR [rsi+984]",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov rax, QWORD PTR [rsi+992]",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov rax, QWORD PTR [rsi+1000]",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov rax, QWORD PTR [rsi+1008]",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov rax, QWORD PTR [rsi+1016]",
+ "mov QWORD PTR [rdi+1016], rax",
+ "228:",
+ "add r15, 1",
+ "cmp r15, r13",
+ "jb 210b",
+ "29:",
+ "add rbx, 1",
+ "cmp rbx, QWORD PTR [rbp+184]",
+ "jb 25b",
+ "add r14, 1",
+ "cmp r14, 4",
+ "jb 24b",
+ "mov rax, QWORD PTR [rbp]",
+ "add rax, 1",
+ "mov QWORD PTR [rbp], rax",
+ "cmp rax, QWORD PTR [rbp+72]",
+ "jb 23b",
+ "mov rdi, QWORD PTR [rbp+232]",
+ "mov rbx, 0",
+ "mov rax, 0",
+ "mov QWORD PTR [rdi], rax",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov QWORD PTR [rdi+1016], rax",
+ "229:",
+ "mov r8, QWORD PTR [rbp+232]",
+ "mov rax, rbx",
+ "mov rcx, r12",
+ "sub rcx, 1",
+ "mul r12",
+ "add rax, rcx",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, rax",
+ "add rax, r8",
+ "mov rsi, rax",
+ "mov rdi, r8",
+ "mov rax, QWORD PTR [rsi]",
+ "xor rax, QWORD PTR [rdi]",
+ "mov QWORD PTR [rdi], rax",
+ "mov rax, QWORD PTR [rsi+8]",
+ "xor rax, QWORD PTR [rdi+8]",
+ "mov QWORD PTR [rdi+8], rax",
+ "mov rax, QWORD PTR [rsi+16]",
+ "xor rax, QWORD PTR [rdi+16]",
+ "mov QWORD PTR [rdi+16], rax",
+ "mov rax, QWORD PTR [rsi+24]",
+ "xor rax, QWORD PTR [rdi+24]",
+ "mov QWORD PTR [rdi+24], rax",
+ "mov rax, QWORD PTR [rsi+32]",
+ "xor rax, QWORD PTR [rdi+32]",
+ "mov QWORD PTR [rdi+32], rax",
+ "mov rax, QWORD PTR [rsi+40]",
+ "xor rax, QWORD PTR [rdi+40]",
+ "mov QWORD PTR [rdi+40], rax",
+ "mov rax, QWORD PTR [rsi+48]",
+ "xor rax, QWORD PTR [rdi+48]",
+ "mov QWORD PTR [rdi+48], rax",
+ "mov rax, QWORD PTR [rsi+56]",
+ "xor rax, QWORD PTR [rdi+56]",
+ "mov QWORD PTR [rdi+56], rax",
+ "mov rax, QWORD PTR [rsi+64]",
+ "xor rax, QWORD PTR [rdi+64]",
+ "mov QWORD PTR [rdi+64], rax",
+ "mov rax, QWORD PTR [rsi+72]",
+ "xor rax, QWORD PTR [rdi+72]",
+ "mov QWORD PTR [rdi+72], rax",
+ "mov rax, QWORD PTR [rsi+80]",
+ "xor rax, QWORD PTR [rdi+80]",
+ "mov QWORD PTR [rdi+80], rax",
+ "mov rax, QWORD PTR [rsi+88]",
+ "xor rax, QWORD PTR [rdi+88]",
+ "mov QWORD PTR [rdi+88], rax",
+ "mov rax, QWORD PTR [rsi+96]",
+ "xor rax, QWORD PTR [rdi+96]",
+ "mov QWORD PTR [rdi+96], rax",
+ "mov rax, QWORD PTR [rsi+104]",
+ "xor rax, QWORD PTR [rdi+104]",
+ "mov QWORD PTR [rdi+104], rax",
+ "mov rax, QWORD PTR [rsi+112]",
+ "xor rax, QWORD PTR [rdi+112]",
+ "mov QWORD PTR [rdi+112], rax",
+ "mov rax, QWORD PTR [rsi+120]",
+ "xor rax, QWORD PTR [rdi+120]",
+ "mov QWORD PTR [rdi+120], rax",
+ "mov rax, QWORD PTR [rsi+128]",
+ "xor rax, QWORD PTR [rdi+128]",
+ "mov QWORD PTR [rdi+128], rax",
+ "mov rax, QWORD PTR [rsi+136]",
+ "xor rax, QWORD PTR [rdi+136]",
+ "mov QWORD PTR [rdi+136], rax",
+ "mov rax, QWORD PTR [rsi+144]",
+ "xor rax, QWORD PTR [rdi+144]",
+ "mov QWORD PTR [rdi+144], rax",
+ "mov rax, QWORD PTR [rsi+152]",
+ "xor rax, QWORD PTR [rdi+152]",
+ "mov QWORD PTR [rdi+152], rax",
+ "mov rax, QWORD PTR [rsi+160]",
+ "xor rax, QWORD PTR [rdi+160]",
+ "mov QWORD PTR [rdi+160], rax",
+ "mov rax, QWORD PTR [rsi+168]",
+ "xor rax, QWORD PTR [rdi+168]",
+ "mov QWORD PTR [rdi+168], rax",
+ "mov rax, QWORD PTR [rsi+176]",
+ "xor rax, QWORD PTR [rdi+176]",
+ "mov QWORD PTR [rdi+176], rax",
+ "mov rax, QWORD PTR [rsi+184]",
+ "xor rax, QWORD PTR [rdi+184]",
+ "mov QWORD PTR [rdi+184], rax",
+ "mov rax, QWORD PTR [rsi+192]",
+ "xor rax, QWORD PTR [rdi+192]",
+ "mov QWORD PTR [rdi+192], rax",
+ "mov rax, QWORD PTR [rsi+200]",
+ "xor rax, QWORD PTR [rdi+200]",
+ "mov QWORD PTR [rdi+200], rax",
+ "mov rax, QWORD PTR [rsi+208]",
+ "xor rax, QWORD PTR [rdi+208]",
+ "mov QWORD PTR [rdi+208], rax",
+ "mov rax, QWORD PTR [rsi+216]",
+ "xor rax, QWORD PTR [rdi+216]",
+ "mov QWORD PTR [rdi+216], rax",
+ "mov rax, QWORD PTR [rsi+224]",
+ "xor rax, QWORD PTR [rdi+224]",
+ "mov QWORD PTR [rdi+224], rax",
+ "mov rax, QWORD PTR [rsi+232]",
+ "xor rax, QWORD PTR [rdi+232]",
+ "mov QWORD PTR [rdi+232], rax",
+ "mov rax, QWORD PTR [rsi+240]",
+ "xor rax, QWORD PTR [rdi+240]",
+ "mov QWORD PTR [rdi+240], rax",
+ "mov rax, QWORD PTR [rsi+248]",
+ "xor rax, QWORD PTR [rdi+248]",
+ "mov QWORD PTR [rdi+248], rax",
+ "mov rax, QWORD PTR [rsi+256]",
+ "xor rax, QWORD PTR [rdi+256]",
+ "mov QWORD PTR [rdi+256], rax",
+ "mov rax, QWORD PTR [rsi+264]",
+ "xor rax, QWORD PTR [rdi+264]",
+ "mov QWORD PTR [rdi+264], rax",
+ "mov rax, QWORD PTR [rsi+272]",
+ "xor rax, QWORD PTR [rdi+272]",
+ "mov QWORD PTR [rdi+272], rax",
+ "mov rax, QWORD PTR [rsi+280]",
+ "xor rax, QWORD PTR [rdi+280]",
+ "mov QWORD PTR [rdi+280], rax",
+ "mov rax, QWORD PTR [rsi+288]",
+ "xor rax, QWORD PTR [rdi+288]",
+ "mov QWORD PTR [rdi+288], rax",
+ "mov rax, QWORD PTR [rsi+296]",
+ "xor rax, QWORD PTR [rdi+296]",
+ "mov QWORD PTR [rdi+296], rax",
+ "mov rax, QWORD PTR [rsi+304]",
+ "xor rax, QWORD PTR [rdi+304]",
+ "mov QWORD PTR [rdi+304], rax",
+ "mov rax, QWORD PTR [rsi+312]",
+ "xor rax, QWORD PTR [rdi+312]",
+ "mov QWORD PTR [rdi+312], rax",
+ "mov rax, QWORD PTR [rsi+320]",
+ "xor rax, QWORD PTR [rdi+320]",
+ "mov QWORD PTR [rdi+320], rax",
+ "mov rax, QWORD PTR [rsi+328]",
+ "xor rax, QWORD PTR [rdi+328]",
+ "mov QWORD PTR [rdi+328], rax",
+ "mov rax, QWORD PTR [rsi+336]",
+ "xor rax, QWORD PTR [rdi+336]",
+ "mov QWORD PTR [rdi+336], rax",
+ "mov rax, QWORD PTR [rsi+344]",
+ "xor rax, QWORD PTR [rdi+344]",
+ "mov QWORD PTR [rdi+344], rax",
+ "mov rax, QWORD PTR [rsi+352]",
+ "xor rax, QWORD PTR [rdi+352]",
+ "mov QWORD PTR [rdi+352], rax",
+ "mov rax, QWORD PTR [rsi+360]",
+ "xor rax, QWORD PTR [rdi+360]",
+ "mov QWORD PTR [rdi+360], rax",
+ "mov rax, QWORD PTR [rsi+368]",
+ "xor rax, QWORD PTR [rdi+368]",
+ "mov QWORD PTR [rdi+368], rax",
+ "mov rax, QWORD PTR [rsi+376]",
+ "xor rax, QWORD PTR [rdi+376]",
+ "mov QWORD PTR [rdi+376], rax",
+ "mov rax, QWORD PTR [rsi+384]",
+ "xor rax, QWORD PTR [rdi+384]",
+ "mov QWORD PTR [rdi+384], rax",
+ "mov rax, QWORD PTR [rsi+392]",
+ "xor rax, QWORD PTR [rdi+392]",
+ "mov QWORD PTR [rdi+392], rax",
+ "mov rax, QWORD PTR [rsi+400]",
+ "xor rax, QWORD PTR [rdi+400]",
+ "mov QWORD PTR [rdi+400], rax",
+ "mov rax, QWORD PTR [rsi+408]",
+ "xor rax, QWORD PTR [rdi+408]",
+ "mov QWORD PTR [rdi+408], rax",
+ "mov rax, QWORD PTR [rsi+416]",
+ "xor rax, QWORD PTR [rdi+416]",
+ "mov QWORD PTR [rdi+416], rax",
+ "mov rax, QWORD PTR [rsi+424]",
+ "xor rax, QWORD PTR [rdi+424]",
+ "mov QWORD PTR [rdi+424], rax",
+ "mov rax, QWORD PTR [rsi+432]",
+ "xor rax, QWORD PTR [rdi+432]",
+ "mov QWORD PTR [rdi+432], rax",
+ "mov rax, QWORD PTR [rsi+440]",
+ "xor rax, QWORD PTR [rdi+440]",
+ "mov QWORD PTR [rdi+440], rax",
+ "mov rax, QWORD PTR [rsi+448]",
+ "xor rax, QWORD PTR [rdi+448]",
+ "mov QWORD PTR [rdi+448], rax",
+ "mov rax, QWORD PTR [rsi+456]",
+ "xor rax, QWORD PTR [rdi+456]",
+ "mov QWORD PTR [rdi+456], rax",
+ "mov rax, QWORD PTR [rsi+464]",
+ "xor rax, QWORD PTR [rdi+464]",
+ "mov QWORD PTR [rdi+464], rax",
+ "mov rax, QWORD PTR [rsi+472]",
+ "xor rax, QWORD PTR [rdi+472]",
+ "mov QWORD PTR [rdi+472], rax",
+ "mov rax, QWORD PTR [rsi+480]",
+ "xor rax, QWORD PTR [rdi+480]",
+ "mov QWORD PTR [rdi+480], rax",
+ "mov rax, QWORD PTR [rsi+488]",
+ "xor rax, QWORD PTR [rdi+488]",
+ "mov QWORD PTR [rdi+488], rax",
+ "mov rax, QWORD PTR [rsi+496]",
+ "xor rax, QWORD PTR [rdi+496]",
+ "mov QWORD PTR [rdi+496], rax",
+ "mov rax, QWORD PTR [rsi+504]",
+ "xor rax, QWORD PTR [rdi+504]",
+ "mov QWORD PTR [rdi+504], rax",
+ "mov rax, QWORD PTR [rsi+512]",
+ "xor rax, QWORD PTR [rdi+512]",
+ "mov QWORD PTR [rdi+512], rax",
+ "mov rax, QWORD PTR [rsi+520]",
+ "xor rax, QWORD PTR [rdi+520]",
+ "mov QWORD PTR [rdi+520], rax",
+ "mov rax, QWORD PTR [rsi+528]",
+ "xor rax, QWORD PTR [rdi+528]",
+ "mov QWORD PTR [rdi+528], rax",
+ "mov rax, QWORD PTR [rsi+536]",
+ "xor rax, QWORD PTR [rdi+536]",
+ "mov QWORD PTR [rdi+536], rax",
+ "mov rax, QWORD PTR [rsi+544]",
+ "xor rax, QWORD PTR [rdi+544]",
+ "mov QWORD PTR [rdi+544], rax",
+ "mov rax, QWORD PTR [rsi+552]",
+ "xor rax, QWORD PTR [rdi+552]",
+ "mov QWORD PTR [rdi+552], rax",
+ "mov rax, QWORD PTR [rsi+560]",
+ "xor rax, QWORD PTR [rdi+560]",
+ "mov QWORD PTR [rdi+560], rax",
+ "mov rax, QWORD PTR [rsi+568]",
+ "xor rax, QWORD PTR [rdi+568]",
+ "mov QWORD PTR [rdi+568], rax",
+ "mov rax, QWORD PTR [rsi+576]",
+ "xor rax, QWORD PTR [rdi+576]",
+ "mov QWORD PTR [rdi+576], rax",
+ "mov rax, QWORD PTR [rsi+584]",
+ "xor rax, QWORD PTR [rdi+584]",
+ "mov QWORD PTR [rdi+584], rax",
+ "mov rax, QWORD PTR [rsi+592]",
+ "xor rax, QWORD PTR [rdi+592]",
+ "mov QWORD PTR [rdi+592], rax",
+ "mov rax, QWORD PTR [rsi+600]",
+ "xor rax, QWORD PTR [rdi+600]",
+ "mov QWORD PTR [rdi+600], rax",
+ "mov rax, QWORD PTR [rsi+608]",
+ "xor rax, QWORD PTR [rdi+608]",
+ "mov QWORD PTR [rdi+608], rax",
+ "mov rax, QWORD PTR [rsi+616]",
+ "xor rax, QWORD PTR [rdi+616]",
+ "mov QWORD PTR [rdi+616], rax",
+ "mov rax, QWORD PTR [rsi+624]",
+ "xor rax, QWORD PTR [rdi+624]",
+ "mov QWORD PTR [rdi+624], rax",
+ "mov rax, QWORD PTR [rsi+632]",
+ "xor rax, QWORD PTR [rdi+632]",
+ "mov QWORD PTR [rdi+632], rax",
+ "mov rax, QWORD PTR [rsi+640]",
+ "xor rax, QWORD PTR [rdi+640]",
+ "mov QWORD PTR [rdi+640], rax",
+ "mov rax, QWORD PTR [rsi+648]",
+ "xor rax, QWORD PTR [rdi+648]",
+ "mov QWORD PTR [rdi+648], rax",
+ "mov rax, QWORD PTR [rsi+656]",
+ "xor rax, QWORD PTR [rdi+656]",
+ "mov QWORD PTR [rdi+656], rax",
+ "mov rax, QWORD PTR [rsi+664]",
+ "xor rax, QWORD PTR [rdi+664]",
+ "mov QWORD PTR [rdi+664], rax",
+ "mov rax, QWORD PTR [rsi+672]",
+ "xor rax, QWORD PTR [rdi+672]",
+ "mov QWORD PTR [rdi+672], rax",
+ "mov rax, QWORD PTR [rsi+680]",
+ "xor rax, QWORD PTR [rdi+680]",
+ "mov QWORD PTR [rdi+680], rax",
+ "mov rax, QWORD PTR [rsi+688]",
+ "xor rax, QWORD PTR [rdi+688]",
+ "mov QWORD PTR [rdi+688], rax",
+ "mov rax, QWORD PTR [rsi+696]",
+ "xor rax, QWORD PTR [rdi+696]",
+ "mov QWORD PTR [rdi+696], rax",
+ "mov rax, QWORD PTR [rsi+704]",
+ "xor rax, QWORD PTR [rdi+704]",
+ "mov QWORD PTR [rdi+704], rax",
+ "mov rax, QWORD PTR [rsi+712]",
+ "xor rax, QWORD PTR [rdi+712]",
+ "mov QWORD PTR [rdi+712], rax",
+ "mov rax, QWORD PTR [rsi+720]",
+ "xor rax, QWORD PTR [rdi+720]",
+ "mov QWORD PTR [rdi+720], rax",
+ "mov rax, QWORD PTR [rsi+728]",
+ "xor rax, QWORD PTR [rdi+728]",
+ "mov QWORD PTR [rdi+728], rax",
+ "mov rax, QWORD PTR [rsi+736]",
+ "xor rax, QWORD PTR [rdi+736]",
+ "mov QWORD PTR [rdi+736], rax",
+ "mov rax, QWORD PTR [rsi+744]",
+ "xor rax, QWORD PTR [rdi+744]",
+ "mov QWORD PTR [rdi+744], rax",
+ "mov rax, QWORD PTR [rsi+752]",
+ "xor rax, QWORD PTR [rdi+752]",
+ "mov QWORD PTR [rdi+752], rax",
+ "mov rax, QWORD PTR [rsi+760]",
+ "xor rax, QWORD PTR [rdi+760]",
+ "mov QWORD PTR [rdi+760], rax",
+ "mov rax, QWORD PTR [rsi+768]",
+ "xor rax, QWORD PTR [rdi+768]",
+ "mov QWORD PTR [rdi+768], rax",
+ "mov rax, QWORD PTR [rsi+776]",
+ "xor rax, QWORD PTR [rdi+776]",
+ "mov QWORD PTR [rdi+776], rax",
+ "mov rax, QWORD PTR [rsi+784]",
+ "xor rax, QWORD PTR [rdi+784]",
+ "mov QWORD PTR [rdi+784], rax",
+ "mov rax, QWORD PTR [rsi+792]",
+ "xor rax, QWORD PTR [rdi+792]",
+ "mov QWORD PTR [rdi+792], rax",
+ "mov rax, QWORD PTR [rsi+800]",
+ "xor rax, QWORD PTR [rdi+800]",
+ "mov QWORD PTR [rdi+800], rax",
+ "mov rax, QWORD PTR [rsi+808]",
+ "xor rax, QWORD PTR [rdi+808]",
+ "mov QWORD PTR [rdi+808], rax",
+ "mov rax, QWORD PTR [rsi+816]",
+ "xor rax, QWORD PTR [rdi+816]",
+ "mov QWORD PTR [rdi+816], rax",
+ "mov rax, QWORD PTR [rsi+824]",
+ "xor rax, QWORD PTR [rdi+824]",
+ "mov QWORD PTR [rdi+824], rax",
+ "mov rax, QWORD PTR [rsi+832]",
+ "xor rax, QWORD PTR [rdi+832]",
+ "mov QWORD PTR [rdi+832], rax",
+ "mov rax, QWORD PTR [rsi+840]",
+ "xor rax, QWORD PTR [rdi+840]",
+ "mov QWORD PTR [rdi+840], rax",
+ "mov rax, QWORD PTR [rsi+848]",
+ "xor rax, QWORD PTR [rdi+848]",
+ "mov QWORD PTR [rdi+848], rax",
+ "mov rax, QWORD PTR [rsi+856]",
+ "xor rax, QWORD PTR [rdi+856]",
+ "mov QWORD PTR [rdi+856], rax",
+ "mov rax, QWORD PTR [rsi+864]",
+ "xor rax, QWORD PTR [rdi+864]",
+ "mov QWORD PTR [rdi+864], rax",
+ "mov rax, QWORD PTR [rsi+872]",
+ "xor rax, QWORD PTR [rdi+872]",
+ "mov QWORD PTR [rdi+872], rax",
+ "mov rax, QWORD PTR [rsi+880]",
+ "xor rax, QWORD PTR [rdi+880]",
+ "mov QWORD PTR [rdi+880], rax",
+ "mov rax, QWORD PTR [rsi+888]",
+ "xor rax, QWORD PTR [rdi+888]",
+ "mov QWORD PTR [rdi+888], rax",
+ "mov rax, QWORD PTR [rsi+896]",
+ "xor rax, QWORD PTR [rdi+896]",
+ "mov QWORD PTR [rdi+896], rax",
+ "mov rax, QWORD PTR [rsi+904]",
+ "xor rax, QWORD PTR [rdi+904]",
+ "mov QWORD PTR [rdi+904], rax",
+ "mov rax, QWORD PTR [rsi+912]",
+ "xor rax, QWORD PTR [rdi+912]",
+ "mov QWORD PTR [rdi+912], rax",
+ "mov rax, QWORD PTR [rsi+920]",
+ "xor rax, QWORD PTR [rdi+920]",
+ "mov QWORD PTR [rdi+920], rax",
+ "mov rax, QWORD PTR [rsi+928]",
+ "xor rax, QWORD PTR [rdi+928]",
+ "mov QWORD PTR [rdi+928], rax",
+ "mov rax, QWORD PTR [rsi+936]",
+ "xor rax, QWORD PTR [rdi+936]",
+ "mov QWORD PTR [rdi+936], rax",
+ "mov rax, QWORD PTR [rsi+944]",
+ "xor rax, QWORD PTR [rdi+944]",
+ "mov QWORD PTR [rdi+944], rax",
+ "mov rax, QWORD PTR [rsi+952]",
+ "xor rax, QWORD PTR [rdi+952]",
+ "mov QWORD PTR [rdi+952], rax",
+ "mov rax, QWORD PTR [rsi+960]",
+ "xor rax, QWORD PTR [rdi+960]",
+ "mov QWORD PTR [rdi+960], rax",
+ "mov rax, QWORD PTR [rsi+968]",
+ "xor rax, QWORD PTR [rdi+968]",
+ "mov QWORD PTR [rdi+968], rax",
+ "mov rax, QWORD PTR [rsi+976]",
+ "xor rax, QWORD PTR [rdi+976]",
+ "mov QWORD PTR [rdi+976], rax",
+ "mov rax, QWORD PTR [rsi+984]",
+ "xor rax, QWORD PTR [rdi+984]",
+ "mov QWORD PTR [rdi+984], rax",
+ "mov rax, QWORD PTR [rsi+992]",
+ "xor rax, QWORD PTR [rdi+992]",
+ "mov QWORD PTR [rdi+992], rax",
+ "mov rax, QWORD PTR [rsi+1000]",
+ "xor rax, QWORD PTR [rdi+1000]",
+ "mov QWORD PTR [rdi+1000], rax",
+ "mov rax, QWORD PTR [rsi+1008]",
+ "xor rax, QWORD PTR [rdi+1008]",
+ "mov QWORD PTR [rdi+1008], rax",
+ "mov rax, QWORD PTR [rsi+1016]",
+ "xor rax, QWORD PTR [rdi+1016]",
+ "mov QWORD PTR [rdi+1016], rax",
+ "add rbx, 1",
+ "cmp rbx, QWORD PTR [rbp+184]",
+ "jb 229b",
+ "mov rdi, QWORD PTR [rbp+232]",
+ "mov rsi, 1024",
+ "mov rdx, QWORD PTR [rbp+256]",
+ "mov rcx, QWORD PTR [rbp+264]",
+ "mov r8, QWORD PTR [rbp+248]",
+ "call {vg_argon2_hprime}",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop rax",
+ "pop r15",
+ "pop r14",
+ "pop r13",
+ "pop r12",
+ "pop rbp",
+ "pop rbx",
+ "ret",
+ vg_blake2b_init = sym super::blake2b::vg_blake2b_init,
+ vg_blake2b_update = sym super::blake2b::vg_blake2b_update,
+ vg_blake2b_finalize = sym super::blake2b::vg_blake2b_finalize,
+ vg_argon2_hprime = sym super::argon2::vg_argon2_hprime,
+ vg_argon2_compress = sym super::argon2::vg_argon2_compress,
+ )
+}
diff --git a/src/lib.rs b/src/lib.rs
index 3cd8f6c5d..f7b12e031 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -80,6 +80,7 @@ compile_error!("32-bit ARM needs an AAPCS target (not Apple's armv7s or armv7k)"
mod aes;
pub mod aes_gcm;
+pub mod argon2;
pub mod chacha20;
pub mod chacha20poly1305;
pub mod cmac;
diff --git a/tests/rfc9106/main.rs b/tests/rfc9106/main.rs
new file mode 100644
index 000000000..d3cb55cbb
--- /dev/null
+++ b/tests/rfc9106/main.rs
@@ -0,0 +1,132 @@
+//! Published RFC 9106 vectors, read from the unmodified RFC, and API boundaries.
+
+#![cfg(all(target_arch = "x86_64", feature = "alloc"))]
+
+use verified_garbage::argon2::{Error, Variant, derive};
+
+fn bytes(text: &str, label: &str, length: usize) -> Vec {
+ text.split_once(label)
+ .unwrap()
+ .1
+ .split_whitespace()
+ .take(length)
+ .map(|s| u8::from_str_radix(s, 16).unwrap())
+ .collect()
+}
+
+fn number(text: &str, label: &str) -> u32 {
+ text.split_once(label)
+ .unwrap()
+ .1
+ .split_whitespace()
+ .next()
+ .unwrap()
+ .trim_end_matches(',')
+ .parse()
+ .unwrap()
+}
+
+fn input(text: &str, label: &str) -> Vec {
+ let rest = text.split_once(&format!("{label}[")).unwrap().1;
+ let (length, data) = rest.split_once("]:").unwrap();
+ data.split_whitespace()
+ .take(length.parse().unwrap())
+ .map(|s| u8::from_str_radix(s, 16).unwrap())
+ .collect()
+}
+
+#[test]
+fn rfc9106_vectors() {
+ let text = include_str!("../../vectors/rfc9106/rfc9106.txt");
+ for (variant, name) in [
+ (Variant::Argon2d, "Argon2d"),
+ (Variant::Argon2i, "Argon2i"),
+ (Variant::Argon2id, "Argon2id"),
+ ] {
+ let text = text
+ .split_once(&format!("{name} version number 19"))
+ .unwrap()
+ .1;
+ let expected = bytes(text, "Tag:", number(text, "Tag length:") as usize);
+ let mut out = vec![0; expected.len()];
+ for threads in [1, 2, 8] {
+ derive(
+ variant,
+ &input(text, "Password"),
+ &input(text, "Salt"),
+ number(text, "Passes:"),
+ number(text, "Memory:"),
+ number(text, "Parallelism:"),
+ threads,
+ &input(text, "Secret"),
+ &input(text, "Associated data"),
+ &mut out,
+ )
+ .unwrap();
+ assert_eq!(out, expected, "{variant:?}, threads={threads}");
+ }
+ }
+}
+
+#[test]
+fn invalid_parameters_preserve_output() {
+ let mut out = [0xa5; 4];
+ for (passes, memory, lanes, threads) in [
+ (0, 8, 1, 1),
+ (1, 8, 0, 1),
+ (1, u32::MAX, 1 << 24, 1),
+ (1, 8, 1, 0),
+ (1, 8, 1, 1 << 24),
+ (1, 7, 1, 1),
+ (1, 15, 2, 1),
+ ] {
+ assert_eq!(
+ derive(
+ Variant::Argon2id,
+ b"",
+ b"",
+ passes,
+ memory,
+ lanes,
+ threads,
+ b"",
+ b"",
+ &mut out
+ ),
+ Err(Error::InvalidParameters)
+ );
+ assert_eq!(out, [0xa5; 4]);
+ }
+ for len in 0..4 {
+ assert_eq!(
+ derive(
+ Variant::Argon2i,
+ b"",
+ b"",
+ 1,
+ 8,
+ 1,
+ 1,
+ b"",
+ b"",
+ &mut out[..len]
+ ),
+ Err(Error::InvalidParameters)
+ );
+ }
+}
+
+#[test]
+fn errors() {
+ for (error, message) in [
+ (Error::InvalidParameters, "invalid Argon2 parameters"),
+ (
+ Error::AllocationFailed,
+ "could not allocate Argon2's memory",
+ ),
+ ] {
+ assert_eq!(error.to_string(), message);
+ let _: &dyn std::error::Error = &error;
+ assert!(!format!("{error:?}").is_empty());
+ }
+}