val free: a:e_alg -> (
  let a = G.reveal a in
  s:state a ->
  ST unit
    (requires (fun h0 ->
      freeable h0 s /\
      invariant h0 s))
    (ensures (fun h0 _ h1 ->
      B.(modifies (footprint h0 s) h0 h1))))
val free: a:e_alg -> (
  let a = G.reveal a in
  s:state a ->
  ST unit
    (requires (fun h0 ->
      freeable h0 s /\
      invariant h0 s))
    (ensures (fun h0 _ h1 ->
      B.(modifies (footprint h0 s) h0 h1))))
let free a p =
  let State i g_iv iv iv_len g_key ek c0 = !*p in
  B.free iv;
  B.free ek;
  B.free p
module NotEverCrypt.CTR

module ST = FStar.HyperStack.ST
module HS = FStar.HyperStack
module B = LowStar.Buffer
module G = FStar.Ghost

friend Spec.Cipher.Expansion
friend EverCrypt.CTR.Keys

open EverCrypt.CTR.Keys
open FStar.HyperStack.ST
open LowStar.BufferOps
open Spec.Cipher.Expansion
open Spec.Agile.CTR

#set-options "--max_fuel 0 --max_ifuel 0"

noextract
let nonce_upper_bound a =
  match a with
  | AES128 | AES256 -> block_length a
  | CHACHA20 -> 12

noeq
type state_s (a: Spec.cipher_alg) =
| State:
    i:impl ->
    g_iv: G.erased (Spec.nonce a) ->
    iv: B.buffer uint8 { B.length iv = nonce_upper_bound a } ->
    iv_len: UInt32.t { UInt32.v iv_len = Seq.length (G.reveal g_iv) } ->
    g_key: G.erased (Spec.key a) ->
    xkey: B.buffer uint8 { B.length xkey = concrete_xkey_length i } ->
    ctr: UInt32.t ->
    state_s a

let freeable_s #a s =
  let State _ _ iv _ _ key _ = s in
  B.freeable iv /\ B.freeable key

let footprint_s #a s =
  let State _ _ iv _ _ key _ = s in
  B.(loc_addr_of_buffer iv `loc_union` loc_addr_of_buffer key)

let cpu_features_invariant (i: impl): Type0 =
  match i with
  | Vale_AES128 | Vale_AES256 ->
      EverCrypt.TargetConfig.hacl_can_compile_vale /\
      Vale.X64.CPU_Features_s.(aesni_enabled /\ pclmulqdq_enabled /\ avx_enabled /\ sse_enabled)
  | Hacl_CHACHA20 ->
      True

let invariant_s #a h s =
  let State i g_iv iv _ g_key ek _ = s in
  let g_iv = G.reveal g_iv in
  a = cipher_alg_of_impl i /\
  B.live h iv /\ B.live h ek /\
  B.disjoint ek iv /\
  g_iv `Seq.equal` Seq.slice (B.as_seq h iv) 0 (Seq.length g_iv) /\
  concrete_expand i (G.reveal g_key) `Seq.equal` B.as_seq h ek /\
  cpu_features_invariant i

let _: squash (inversion impl) = allow_inversion impl
let _: squash (inversion cipher_alg) = allow_inversion cipher_alg

let invert_state_s (a: alg): Lemma
  (requires True)
  (ensures (inversion (state_s a)))
  [ SMTPat (state_s a) ]
=
  allow_inversion (state_s a)

let invariant_loc_in_footprint #_ _ _ = ()

let frame_invariant #_ _ _ _ _ = ()

let kv #a (s: state_s a) =
  let State _ _ _ _ g_key _ _ = s in
  G.reveal g_key

let iv #a (s: state_s a) =
  let State _ g_iv _ _ _ _ _ = s in
  G.reveal g_iv

let ctr #a (h: HS.mem) (s: state a) =
  UInt32.v (State?.ctr (B.deref h s))

let alg_of_state _ s =
  let State i _ _ _ _ _ _ = !*s in
  cipher_alg_of_impl i

let vale_impl_of_alg (a: vale_cipher_alg): vale_impl =
  match a with
  | AES128 -> Vale_AES128
  | AES256 -> Vale_AES256

friend Lib.IntTypes

#push-options "--z3rlimit 100"

inline_for_extraction noextract
let create_in_vale (i: vale_impl): create_in_st (cipher_alg_of_impl i) =
fun r dst k iv iv_len c ->
  let has_aesni = EverCrypt.AutoConfig2.has_aesni () in
  let has_pclmulqdq = EverCrypt.AutoConfig2.has_pclmulqdq () in
  let has_avx = EverCrypt.AutoConfig2.has_avx() in
  let has_sse = EverCrypt.AutoConfig2.has_sse() in
  [@inline_let]
  let a = cipher_alg_of_impl i in

  if iv_len `UInt32.lt` 12ul then
    InvalidIVLength

  else if EverCrypt.TargetConfig.hacl_can_compile_vale && (has_aesni && has_pclmulqdq && has_avx && has_sse) then
    (**) let h0 = ST.get () in
    (**) let g_iv = G.hide (B.as_seq h0 iv) in
    (**) let g_key: G.erased (key a) = G.hide (B.as_seq h0 (k <: B.buffer uint8)) in

    let ek = B.malloc r 0uy (concrete_xkey_len i) in
    vale_expand i k ek;
    (**) let h1 = ST.get () in
    (**) B.modifies_only_not_unused_in B.loc_none h0 h1;

    let iv' = B.malloc r 0uy 16ul in
    B.blit iv 0ul iv' 0ul iv_len;
    (**) let h2 = ST.get () in
    (**) B.modifies_only_not_unused_in B.loc_none h0 h2;

    let p = B.malloc r (State (vale_impl_of_alg a) g_iv iv' iv_len g_key ek c) 1ul in
    (**) let h3 = ST.get () in
    (**) B.modifies_only_not_unused_in B.loc_none h0 h3;
    assert (B.fresh_loc (footprint h3 p) h0 h3);

    dst *= p;
    (**) let h4 = ST.get () in
    (**) B.modifies_only_not_unused_in B.(loc_buffer dst) h0 h4;

    Success

  else
    UnsupportedAlgorithm

let create_in a r dst k iv iv_len c =
  match a with
  | AES128 ->
      create_in_vale Vale_AES128 r dst k iv iv_len c
  | AES256 ->
      create_in_vale Vale_AES256 r dst k iv iv_len c
  | CHACHA20 ->
      (**) let h0 = ST.get () in
      (**) let g_iv = G.hide (B.as_seq h0 iv) in
      (**) let g_key: G.erased (key a) = G.hide (B.as_seq h0 (k <: B.buffer uint8)) in

      [@inline_let]
      let l = concrete_xkey_len Hacl_CHACHA20 in
      let ek = B.malloc r 0uy l in
      B.blit k 0ul ek 0ul l;
      (**) let h1 = ST.get () in
      (**) B.modifies_only_not_unused_in B.loc_none h0 h1;

      let iv' = B.malloc r 0uy iv_len in
      B.blit iv 0ul iv' 0ul iv_len;
      (**) let h2 = ST.get () in
      (**) B.modifies_only_not_unused_in B.loc_none h0 h2;

      let p = B.malloc r (State Hacl_CHACHA20 g_iv iv' 12ul g_key ek c) 1ul in
      (**) let h3 = ST.get () in
      (**) B.modifies_only_not_unused_in B.loc_none h0 h3;
      assert (B.fresh_loc (footprint h3 p) h0 h3);

      dst *= p;
      (**) let h4 = ST.get () in
      (**) B.modifies_only_not_unused_in B.(loc_buffer dst) h0 h4;

      Success

inline_for_extraction noextract
let copy_or_expand (i: impl)
  (k: B.buffer uint8 { B.length k = Spec.key_length (cipher_alg_of_impl i) })
  (ek: B.buffer uint8 { B.len ek = concrete_xkey_len i }):
  Stack unit
    (requires (fun h0 ->
      B.live h0 k /\ B.live h0 ek /\
      B.disjoint k ek /\
      cpu_features_invariant i))
    (ensures (fun h0 _ h1 ->
      B.(modifies (loc_buffer ek) h0 h1) /\
      B.as_seq h1 ek `Seq.equal` concrete_expand i (B.as_seq h0 k)))
=
  match i with
  | Vale_AES128 ->
    if EverCrypt.TargetConfig.hacl_can_compile_vale then
        vale_expand Vale_AES128 k ek
  | Vale_AES256 ->
    if EverCrypt.TargetConfig.hacl_can_compile_vale then
        vale_expand Vale_AES256 k ek
  | Hacl_CHACHA20 ->
      B.blit k 0ul ek 0ul 32ul

let init a p k iv iv_len c =
  let State i _ iv' _ _ ek _ = !*p in
  [@inline_let]
  let k: B.buffer uint8 = k in

  (**) let h0 = ST.get () in
  (**) let g_iv = G.hide (B.as_seq h0 iv) in
  (**) let g_key: G.erased (key (cipher_alg_of_impl i)) = G.hide (B.as_seq h0 k) in

  B.blit iv 0ul iv' 0ul iv_len;
  (**) let h1 = ST.get () in
  (**) assert B.(modifies (footprint_s (B.deref h0 p)) h0 h1);

  copy_or_expand i k ek;
  (**) let h2 = ST.get () in
  (**) assert B.(modifies (footprint_s (B.deref h0 p)) h1 h2);

  // TODO: two in-place updates
  p *= (State i g_iv iv' iv_len g_key ek c)

noextract
let as_vale_key (i: vale_impl) (k: key (cipher_alg_of_impl i)):
  s:Seq.seq Vale.Def.Types_s.nat32 {
    Vale.AES.AES_s.is_aes_key_LE (vale_alg_of_vale_impl i) s
  }
=
  let open Vale.Def.Words.Seq_s in
  let k_nat = seq_uint8_to_seq_nat8 k in
  let k_w = seq_nat8_to_seq_nat32_LE k_nat in
  k_w

// Main (missing) proof of functional equivalence between Vale and HACL.
let vale_encrypt_is_hacl_encrypt (i: vale_impl)
  (k: key (cipher_alg_of_impl i))
  (ctr_block: Seq.lseq uint8 16)
  (input: Seq.lseq uint8 16):
  Lemma
    (ensures (
      let open Vale.Def.Words_s in
      let open Vale.Def.Types_s in
      let open Vale.Def.Words.Seq_s in
      // Vale version
      let a = vale_alg_of_vale_impl i in
      let ctr_block_nat8 = seq_uint8_to_seq_nat8 ctr_block in
      let k_nat32 = as_vale_key i k in
      let input_nat8 = seq_uint8_to_seq_nat8 input in
      let cipher_nat8 = Vale.AES.GCTR_s.gctr_encrypt_LE
        (le_bytes_to_quad32 ctr_block_nat8) (Vale.AES.GCTR.make_gctr_plain_LE input_nat8)
        a k_nat32
      in
      let cipher = seq_nat8_to_seq_uint8 cipher_nat8 in

      // HACL version
      let a = aes_alg_of_alg (cipher_alg_of_impl i) in
      let cipher' = Spec.Loops.seq_map2 xor8 input
        Spec.AES.(aes_encrypt_block a (aes_key_expansion a k) ctr_block)
      in
      cipher `Seq.equal` cipher'))
=
  admit ()

inline_for_extraction noextract
let gctr_bytes (i: vale_impl): Vale.Wrapper.X64.GCTR.gctr_bytes_st (vale_alg_of_vale_impl i) =
  match i with
  | Vale_AES128 -> Vale.Wrapper.X64.GCTR.gctr_bytes_stdcall128
  | Vale_AES256 -> Vale.Wrapper.X64.GCTR.gctr_bytes_stdcall256

friend Spec.Agile.Cipher

inline_for_extraction
let uint128_of_uint32 (x: UInt32.t): y:UInt128.t { UInt128.v y = UInt32.v x } =
  let open FStar.Int.Cast.Full in
  uint64_to_uint128 (uint32_to_uint64 x)

inline_for_extraction noextract
let update_block_vale (i: vale_impl): update_block_st (cipher_alg_of_impl i) =
fun p dst src ->
  let State _ g_iv iv iv_len g_key ek c0 = !*p in

  let open Vale.Wrapper.X64.GCTR in
  let open LowStar.Endianness in
  push_frame ();

  // Prepare the block. See Spec.AES.aes_ctr_key_block for instance.
  let ctr_block = B.alloca (0uy <: uint8) 16ul in
  (**) let h0 = ST.get () in

  B.blit iv 0ul ctr_block 0ul iv_len;
  (**) let h1 = ST.get () in

  // Proof steps missing:
  // - show that repeati is equivalent to a blit
  // - show that the uint128 addition does what aes_ctr_block_add_counter
  //   does (the right lemmas should be there already...)
  (* assert (
    let open Lib.ByteSequence in
    let open Lib.IntTypes in
    let open Lib.Sequence in
    let open Lib.LoopCombinators in
    let block0 = B.as_seq h0 ctr_block in
    let spec0 = create 16 (u8 0) in
    let iv = B.as_seq h0 iv in
    let block1 = B.as_seq h1 ctr_block in
    let spec1 =
      repeati #(lbytes 16) (length iv) (fun i b -> b.[i] <- Seq.index iv i) spec0
    in
    block0 `Seq.equal` spec0 /\
    block1 `Seq.equal` spec1
  );
  admit () | _ -> admit () *)

  // Interpreting potential overflow bytes of the IV as part of a 128-bit
  // counter dictated by HACL* spec.
  let c: UInt128.t = load128_be ctr_block `UInt128.add_mod` (uint128_of_uint32 c0) in
  store128_le ctr_block c;
  (**) let h2 = ST.get () in
  (**) Vale.Arch.BufferFriend.lemma_be_to_n_is_nat_from_bytes (B.as_seq h1 ctr_block);
  (**) Vale.Arch.BufferFriend.lemma_n_to_be_is_nat_to_bytes 16 (UInt128.v c);

  gctr_bytes i
    (G.elift1 (as_vale_key i) g_key)
    src 16uL
    dst
    (B.sub ek 0ul (key_offset i))
    ctr_block;
  (**) vale_encrypt_is_hacl_encrypt i (G.reveal g_key) (B.as_seq h2 ctr_block)
    (B.as_seq h2 src);

  let c = c0 `UInt32.add_mod` 1ul in
  p *= (State #(cipher_alg_of_impl i) i g_iv iv iv_len g_key ek c);

  pop_frame ();

  admit ()

let update_block a p dst src =
  let State i g_iv iv iv_len g_key ek c0 = !*p in
  match i with
  | Vale_AES128 ->
    if EverCrypt.TargetConfig.hacl_can_compile_vale then
        update_block_vale Vale_AES128 p dst src

  | Vale_AES256 ->
    if EverCrypt.TargetConfig.hacl_can_compile_vale then
        update_block_vale Vale_AES256 p dst src

  | Hacl_CHACHA20 ->
      let open Hacl.Impl.Chacha20 in
      push_frame ();
      let ctx = B.alloca 0ul 16ul in
      // NOTE: chacha20_init must be called with 0ul because encrypt_block also
      // takes a counter argument and increments too! There may be a way to keep
      // the allocated initial block in the state, but not doing that for now.
      chacha20_init ctx ek (B.sub iv 0ul 12ul) 0ul;
      chacha20_encrypt_block ctx dst c0 src;

      pop_frame ();

      // There's a non-trivial proof of spec equivalence here. See discussion in
      // Spec.Chacha20.
      admit ()
/
Vale.X64.CPU_Features_s.fst.checked Vale.Wrapper.X64.GCTR.fsti.checked Vale.Def.Words_s.fsti.checked Vale.Def.Words.Seq_s.fsti.checked Vale.Def.Types_s.fst.checked Vale.Arch.BufferFriend.fsti.checked Vale.AES.GCTR_s.fst.checked Vale.AES.GCTR.fsti.checked Vale.AES.AES_s.fst.checked Spec.Loops.fst.checked Spec.Cipher.Expansion.fst.checked Spec.Agile.CTR.fst.checked Spec.Agile.Cipher.fst.checked Spec.AES.fst.checked prims.fst.checked LowStar.Endianness.fst.checked LowStar.BufferOps.fst.checked LowStar.Buffer.fst.checked Lib.IntTypes.fst.checked Hacl.Impl.Chacha20.fst.checked FStar.UInt8.fsti.checked FStar.UInt64.fsti.checked FStar.UInt32.fsti.checked FStar.UInt128.fsti.checked FStar.Seq.fst.checked FStar.Pervasives.fsti.checked FStar.Int.Cast.Full.fst.checked FStar.HyperStack.ST.fsti.checked FStar.HyperStack.fst.checked FStar.Ghost.fsti.checked EverCrypt.TargetConfig.fsti.checked EverCrypt.CTR.Keys.fst.checked EverCrypt.AutoConfig2.fsti.checked
NotEverCrypt.CTR.fst
a: NotEverCrypt.CTR.e_alg
  -> (let a = FStar.Ghost.reveal a in
      s: NotEverCrypt.CTR.state a -> FStar.HyperStack.ST.ST Prims.unit)
NotEverCrypt.CTR.e_alg NotEverCrypt.CTR.state FStar.Ghost.reveal NotEverCrypt.CTR.alg Spec.Cipher.Expansion.impl FStar.Ghost.erased Spec.Agile.Cipher.nonce LowStar.Buffer.buffer EverCrypt.CTR.Keys.uint8 Prims.b2t Prims.op_Equality Prims.int LowStar.Monotonic.Buffer.length LowStar.Buffer.trivial_preorder NotEverCrypt.CTR.nonce_upper_bound FStar.UInt32.t Prims.l_or FStar.UInt.size FStar.UInt32.n Prims.op_GreaterThanOrEqual FStar.UInt32.v FStar.Seq.Base.length Lib.IntTypes.uint_t Lib.IntTypes.U8 Lib.IntTypes.SEC Spec.Agile.Cipher.key Prims.nat Spec.Cipher.Expansion.concrete_xkey_length LowStar.Monotonic.Buffer.free NotEverCrypt.CTR.state_s Prims.unit Spec.Agile.Cipher.cipher_alg LowStar.BufferOps.op_Bang_Star
let free a p =
  
let State i g_iv iv iv_len g_key ek c0 = !*p in
B.free iv;
B.free ek;
B.free p