Require Crypto.Algebra.Hierarchy.
Require Import Crypto.Util.Notations.
(*Require Crypto.Curves.Montgomery.XZ.*)

Module MxDH. (* from RFC7748 *)
  Section MontgomeryLadderKeyExchange.
    Context {F Feq Fzero Fone Fopp Fadd Fsub Fmul Finv Fdiv} {field:@Algebra.Hierarchy.field F Feq Fzero Fone Fopp Fadd Fsub Fmul Finv Fdiv} {Feq_dec:Decidable.DecidableRel Feq}.
    Local Infix "=" := Feq : type_scope. Local Notation "a <> b" := (not (a = b)) : type_scope.
    Local Infix "+" := Fadd. Local Infix "*" := Fmul.
    Local Infix "-" := Fsub. Local Infix "/" := Fdiv.
    Local Notation "x ^ 2" := (x*x).
    Local Notation "0" := Fzero.  Local Notation "1" := Fone.
    Local Notation "2" := (1+1). Local Notation "4" := (2+2).

    Context {a b: F}. (* parameters of the Montgomery curve *)
    Context {nonsquare_aa_m4:~exists sqrt, sqrt^2 = a^2-4} {five_neq_zero:1+4<>0}.

    (* Implemention from RFC7748. Note that this differs from EFD and
       Montgomery '87 in the choice of which precomputed constant to use
       for [a] *)
    (* Ideally we would want to verify that this correspons to
       x-coordinate addition on the relevant elliptic curve, as
       specified by addition formulas in Montgomery coordinates.
       However, even if we did get to doing that, we still would not
       want to change the type of the ladder function to talk about
       points that are on the curve -- there are contexts where this
       can be, carefully, used with values that are not verified to
       be on the curve *)
    Context {a24:F} {a24_correct:4*a24 = a-2}.
    Section generic.
      Context (F4 : Type)
              (pair4 : F -> F -> F -> F -> F4)
              (let_in : F -> (F -> F4) -> F4).
      Local Notation "'llet' x := y 'in' z" := (let_in y (fun x => z)).
      Definition ladderstep_gen (X1:F) (P1 P2:F * F) : F4 :=
        let '(X2, Z2) := P1 in
        let '(X3, Z3) := P2 in
        llet A := X2+Z2 in
        llet AA := A^2 in
        llet B := X2-Z2 in
        llet BB := B^2 in
        llet E := AA-BB in
        llet C := X3+Z3 in
        llet D := X3-Z3 in
        llet DA := D*A in
        llet CB := C*B in
        llet X5 := (DA+CB)^2 in
        llet Z5 := X1*(DA-CB)^2 in
        llet X4 := AA*BB in
        llet Z4 := E*(AA + a24*E) in
        pair4 X4 Z4 X5 Z5.
    End generic.

    (** TODO: Make this the only ladderstep? *)
    Definition ladderstep_other_assoc (X1:F) (P1 P2:F*F) : F*F*F*F :=
      Eval cbv beta delta [ladderstep_gen] in
        @ladderstep_gen
            (F*F*F*F)
            (fun X3 Y3 Z3 T3 => (X3, Y3, Z3, T3))
            (fun x f => let y := x in f y)
            X1 P1 P2.

    Definition ladderstep (X1:F) (P1 P2:F*F) : (F*F)*(F*F) :=
      Eval cbv beta delta [ladderstep_gen] in
        @ladderstep_gen
            ((F*F)*(F*F))
            (fun X3 Y3 Z3 T3 => ((X3, Y3), (Z3, T3)))
            (fun x f => let y := x in f y)
            X1 P1 P2.

    (*Import Curves.Montgomery.XZ.
    Goal forall X1 P1 P2, Logic.eq (ladderstep X1 P1 P2) (@M.xzladderstep F Fadd Fsub Fmul a24 X1 P1 P2).
    Proof.
      intros ? [? ?] [? ?].
      reflexivity.
    Qed.*)

    Context {cswap:bool->F*F->F*F->(F*F)*(F*F)}.

    Fixpoint downto_iter (i:nat) : list nat :=
      match i with
      | Datatypes.S i' => cons i' (downto_iter i')
      | O => nil
      end.

    Definition downto {state} init count (step:state->nat->state) : state :=
      List.fold_left step (downto_iter count) init.

    Local Notation xor := Coq.Init.Datatypes.xorb.

    (* Ideally, we would verify that this corresponds to x coordinate
    multiplication *)
    Definition montladder bound (testbit:nat->bool) (u:F) :=
      let '(P1, P2, swap) :=
          downto
            ((1, 0), (u, 1), false)
            bound
            (fun state i =>
               let '(P1, P2, swap) := state in
               let s_i := testbit i in
               let swap := xor swap s_i in
               let '(P1, P2) := cswap swap P1 P2 in
               let swap := s_i in
               let '(P1, P2) := ladderstep u P1 P2 in
               (P1, P2, swap)
            ) in
      let '((x, z), _) := cswap swap P1 P2 in
      x * Finv z.
  End MontgomeryLadderKeyExchange.
End MxDH.

(* see [Test.Curve25519SpecTestVectors] for sanity-checks *)
