(*****************************************************************************)
(*                                                                           *)
(* Open Source License                                                       *)
(* Copyright (c) 2018 Dynamic Ledger Solutions, Inc. <contact@tezos.com>     *)
(*                                                                           *)
(* Permission is hereby granted, free of charge, to any person obtaining a   *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense,  *)
(* and/or sell copies of the Software, and to permit persons to whom the     *)
(* Software is furnished to do so, subject to the following conditions:      *)
(*                                                                           *)
(* The above copyright notice and this permission notice shall be included   *)
(* in all copies or substantial portions of the Software.                    *)
(*                                                                           *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL   *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING   *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER       *)
(* DEALINGS IN THE SOFTWARE.                                                 *)
(*                                                                           *)
(*****************************************************************************)

module Sp = Libsecp256k1.Internal

module type SCALAR_SIG = sig
  type t
  include S.B58_DATA with type t := t
  include S.ENCODER with type t := t
  val zero : t
  val one : t
  val of_Z : Z.t -> t
  val to_Z : t -> Z.t
  val of_int : int -> t
  val add: t -> t -> t
  val mul: t -> t -> t
  val negate: t -> t
  val sub : t -> t -> t
  val of_bits_exn: string -> t
  val to_bits: t -> string
  val inverse: t -> t option
  val pow: t -> Z.t -> t
  val equal : t -> t -> bool
end

module Group : sig
  val order: Z.t
  module Scalar : SCALAR_SIG
  type t
  include S.B58_DATA with type t := t
  include S.ENCODER with type t := t
  val e: t
  val g: t
  val h: t
  val of_coordinates: x:Z.t -> y:Z.t -> t
  val of_bits_exn: string -> t
  val to_bits: t -> string
  val mul: Scalar.t -> t -> t
  val (+): t -> t -> t
  val (-): t -> t -> t
  val (=): t -> t -> bool
end = struct

  let order = Z.of_string_base 16 "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"

  let string_rev s =
    let len = String.length s in
    String.init len (fun i -> s.[len - 1 - i])

  let b32_of_Z z =
    let cs = Cstruct.create 32 in
    let bits = Z.to_bits z in
    let length = (min 32 (String.length bits)) in
    let bits =  String.sub bits 0 length in
    let bits = string_rev bits in
    Cstruct.blit_from_string bits 0 cs (32 - length) length;
    cs

  let z_of_b32 b =
    b |> Cstruct.to_string |> string_rev |> Z.of_bits

  module Scalar : SCALAR_SIG with type t = Sp.Scalar.t = struct
    type t = Sp.Scalar.t

    let zero = Sp.Scalar.zero ()
    let one  = Sp.Scalar.one  ()
    let equal x y = Sp.Scalar.equal x y


    let of_Z z =
      let z = Z.erem z order in
      let r = Sp.Scalar.const () in
      let cs = b32_of_Z z in
      let _ = Sp.Scalar.set_b32 r cs in r

    let to_Z s =
      let cs = Cstruct.create 32 in
      Sp.Scalar.get_b32 cs s; cs |> z_of_b32

    let of_int i = i |> Z.of_int |> of_Z

    let pow t n =
      Z.powm (to_Z t) n order |> of_Z

    let add x y =
      let r = Sp.Scalar.const () in
      let _ = Sp.Scalar.add r x y in r

    let mul x y =
      let r = Sp.Scalar.const () in
      Sp.Scalar.mul r x y; r

    let negate x =
      let r = Sp.Scalar.const () in
      Sp.Scalar.negate r x; r

    let sub x y =
      add x (negate y)

    let of_bits_exn bits =
      let r = Sp.Scalar.const () in
      (* trim to 32 bytes *)
      let cs = Cstruct.create 32 in
      Cstruct.blit_from_string bits 0 cs 0 (min (String.length bits) 32);
      (* ignore overflow condition, it's always 0 based on the c-code *)
      let _ = Sp.Scalar.set_b32 r cs in r

    (* TODO, check that we are less than the order *)

    let to_bits x =
      let c = Cstruct.create 32 in
      Sp.Scalar.get_b32 c x; Cstruct.to_string c

    let inverse x =
      if x = zero then
        None else
        let r = Sp.Scalar.const () in
        Sp.Scalar.inverse r x; Some r

    type Base58.data +=
      | Data of t

    let b58check_encoding =
      Base58.register_encoding
        ~prefix: Base58.Prefix.secp256k1_scalar
        ~length: 32
        ~to_raw: to_bits
        ~of_raw: (fun s -> try Some (of_bits_exn s) with _ -> None)
        ~wrap: (fun x -> Data x)

    let title = "Secp256k1_group.Scalar"
    let name =  "Anscalar for the secp256k1 group"

    include Helpers.MakeB58(struct
        type nonrec t = t
        let title = title
        let name = name
        let b58check_encoding = b58check_encoding
      end)

    include Helpers.MakeEncoder(struct
        type nonrec t = t
        let name = name
        let title = title
        let raw_encoding = Data_encoding.(conv to_bits of_bits_exn string)
        let to_b58check = to_b58check
        let to_short_b58check = to_short_b58check
        let of_b58check = of_b58check
        let of_b58check_opt = of_b58check_opt
        let of_b58check_exn = of_b58check_exn
      end)
  end

  type t = Sp.Group.Jacobian.t
  (* type ge = Sp.Group.ge *)

  let field_of_Z z =
    let fe = Sp.Field.const () in
    let cs = b32_of_Z z in
    let _ = Sp.Field.set_b32 fe cs in fe

  let group_of_jacobian j =
    let r = Sp.Group.of_fields () in
    Sp.Group.Jacobian.get_ge r j; r

  let jacobian_of_group g =
    let j = Sp.Group.Jacobian.of_fields () in
    Sp.Group.Jacobian.set_ge j g; j


  let of_coordinates ~x ~y =
    Sp.Group.of_fields
      ~x:(field_of_Z x) ~y:(field_of_Z y) () |> jacobian_of_group

  let e =
    Sp.Group.Jacobian.of_fields ~infinity:true ()

  let g =
    let gx = Z.of_string "55066263022277343669578718895168534326250603453777594175500187360389116729240"
    and gy = Z.of_string "32670510020758816978083085130507043184471273380659243275938904335757337482424" in
    of_coordinates ~x:gx ~y:gy

  (* To obtain the second generator, take the sha256 hash of the decimal representation of g1_y
       python -c "import hashlib;print int(hashlib.sha256('32670510020758816978083085130507043184471273380659243275938904335757337482424').hexdigest(),16)"
  *)
  let h =
    let hx = Z.of_string "54850469061264194188802857211425616972714231399857248865148107587305936171824"
    and hy = Z.of_string "6558914719042992724977242403721980463337660510165027616783569279181206179101" in
    of_coordinates ~x:hx ~y:hy

  let (+) x y =
    let r = Sp.Group.Jacobian.of_fields () in
    Sp.Group.Jacobian.add_var r x y; r

  let (-) x y =
    let neg_y = Sp.Group.Jacobian.of_fields () in
    Sp.Group.Jacobian.neg neg_y y; x + neg_y

  let (=) x y = Sp.Group.Jacobian.is_infinity (x - y)

  let mul s g =
    let r = Sp.Group.Jacobian.of_fields () in
    Sp.Group.Jacobian.mul r (group_of_jacobian g) s; r

  let to_bits j =
    let x = group_of_jacobian j
    and buf = Cstruct.create 33 in
    let cs =  (Sp.Group.to_pubkey ~compress:true buf x) in
    Cstruct.to_string cs

  let of_bits_exn bits =
    let buf = Cstruct.of_string bits
    and x = Sp.Group.of_fields () in
    Sp.Group.from_pubkey x buf;
    x |> jacobian_of_group


  module Encoding = struct
    type Base58.data +=
      | Data of t

    let title = "Secp256k1_group.Group"
    let name =  "An element of secp256k1"

    let b58check_encoding =
      Base58.register_encoding
        ~prefix: Base58.Prefix.secp256k1_element
        ~length: 33
        ~to_raw: to_bits
        ~of_raw: (fun s -> try Some (of_bits_exn s) with _ -> None)
        ~wrap: (fun x -> Data x)

    include Helpers.MakeB58(
      struct
        type nonrec t = t
        let title = title
        let name = name
        let b58check_encoding = b58check_encoding
      end)

    include Helpers.MakeEncoder(
      struct
        type nonrec t = t
        let name = name
        let title = title
        let raw_encoding = Data_encoding.(conv to_bits of_bits_exn string)
        let to_b58check = to_b58check
        let to_short_b58check = to_short_b58check
        let of_b58check = of_b58check
        let of_b58check_opt = of_b58check_opt
        let of_b58check_exn = of_b58check_exn
      end
      )
  end

  include Encoding

end
