(* LO-Stream: Chunk Confidentiality + Integrity (Theorem 13, P1+P2)
 *
 * Adapted from CryptoVerif's TLS 1.3 Record Protocol example.
 *
 * IND-CPA: Bit-guessing game — adversary submits two equal-length plaintexts,
 * receives encryption of one based on secret bit b0. Advantage = Pr[guess b0].
 *
 * INT-CTXT: Injective correspondence — if decryption succeeds at (count, msg),
 * encryption must have produced (count, msg).
 *
 * Nonce uniqueness enforced via table lookup (nonce-respecting adversary, §9.11(f)).
 * Nonce derivation: chunk_nonce = xor(base_nonce, count) with injectivity equation.
 *
 * Reduces to: XChaCha20-Poly1305 IND-CPA + INT-CTXT.
 *)

type key [fixed, large].
type seqn [fixed].
type nonce_t [fixed, large].
type add_data [bounded].

param N_enc, N_dec.

(* ---------- Nonce derivation with injectivity ---------- *)
(* §15.2: chunk_nonce = base_nonce XOR mask(index, tag_byte)
 * Modeled as xor(iv, count) per TLS 1.3 record protocol pattern.
 * CryptoVerif needs the explicit injectivity equation. *)

fun xor(key, seqn): nonce_t.

equation forall k: key, n: seqn, n': seqn;
    (xor(k, n) = xor(k, n')) = (n = n').

(* ---------- AEAD (IND-CPA + INT-CTXT under unique nonces) ---------- *)

proba P_cpa.
proba P_ctxt.

expand AEAD_nonce(key, bitstring, bitstring, add_data, nonce_t,
                  enc, dec, injbot, Z, P_cpa, P_ctxt).

(* Per-chunk AAD includes base_nonce + count (§15.4) *)
fun derive_aad(key, seqn): add_data [data].

letfun stream_encrypt(k: key, n: nonce_t, m: bitstring, aad: add_data) =
    enc(m, aad, k, n).
letfun stream_decrypt(k: key, n: nonce_t, c: bitstring, aad: add_data) =
    dec(c, aad, k, n).

(* ---------- Tables for nonce uniqueness (§9.11(f) game hypothesis) ---------- *)

table table_enc_nonce(seqn).
table table_dec_nonce(seqn).

(* ---------- Security queries ---------- *)

(* P1 (IND-CPA): secret bit indistinguishable *)
query secret b0 [cv_bit].

(* P2 (INT-CTXT): injective correspondence *)
event Sent(seqn, bitstring).
event Received(seqn, bitstring).

query count: seqn, msg: bitstring;
    inj-event(Received(count, msg)) ==> inj-event(Sent(count, msg))
    public_vars b0.

(* ---------- Channels ---------- *)

channel c_start, c_ready, c_enc_in, c_enc_out, c_dec_in, c_dec_out.

(* ---------- Encrypt oracle ---------- *)
(* Adversary submits (m0, m1, count). Oracle encrypts m_b where b = b0.
 * Count must not have been used before (nonce-respecting). *)

let Encrypt(k: key, iv: key, b: bool) =
    !N_enc
    in(c_enc_in, (clear1: bitstring, clear2: bitstring, count: seqn));
    (* Nonce-respecting: reject reused counter *)
    get table_enc_nonce(=count) in yield else
    insert table_enc_nonce(count);
    (* Equal-length requirement for IND-CPA *)
    if Z(clear1) = Z(clear2) then
    let clear = if_fun(b, clear1, clear2) in
    let aad: add_data = derive_aad(iv, count) in
    let nonce = xor(iv, count) in
    event Sent(count, clear);
    let cipher = stream_encrypt(k, nonce, clear, aad) in
    out(c_enc_out, cipher).

(* ---------- Decrypt oracle ---------- *)
(* Adversary submits (ciphertext, count). Must not reuse count. *)

let Decrypt(k: key, iv: key) =
    !N_dec
    in(c_dec_in, (cipher: bitstring, count: seqn));
    get table_dec_nonce(=count) in yield else
    insert table_dec_nonce(count);
    let aad: add_data = derive_aad(iv, count) in
    let nonce = xor(iv, count) in
    let injbot(clear) = stream_decrypt(k, nonce, cipher, aad) in
    event Received(count, clear).

(* ---------- Main process ---------- *)

process
    in(c_start, ());
    new b0: bool;
    new k: key;
    new iv: key;    (* base_nonce — public *)
    out(c_ready, iv);
    (Encrypt(k, iv, b0) | Decrypt(k, iv))

(* EXPECTED
All queries proved.
END *)
