/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
#include "ellcurve/Montgomery.h"
#include "ellcurve/TwEdwards.h"
#include "openssl/digest.hpp"
#include "openssl/rand.hpp"
#include 
#include 
#include "td/utils/buffer.h"
namespace crypto {
namespace Ed25519 {
const int privkey_bytes = 32;
const int pubkey_bytes = 32;
const int sign_bytes = 64;
const int shared_secret_bytes = 32;
class PublicKey {
  enum { pk_empty, pk_xz, pk_init } inited;
  unsigned char pubkey[pubkey_bytes];
  ellcurve::TwEdwardsCurve::SegrePoint PubKey;
  ellcurve::MontgomeryCurve::PointXZ PubKey_xz;
 public:
  PublicKey() : inited(pk_empty), PubKey(ellcurve::Fp25519()), PubKey_xz(ellcurve::Fp25519()) {
  }
  PublicKey(const unsigned char pub_key[pubkey_bytes]);
  PublicKey(td::Slice pub_key) : PublicKey(pub_key.ubegin()) {
    CHECK(pub_key.size() == pubkey_bytes);
  }
  PublicKey(const ellcurve::TwEdwardsCurve::SegrePoint &Pub_Key);
  bool import_public_key(const unsigned char pub_key[pubkey_bytes]);
  bool import_public_key(td::Slice pub_key) {
    CHECK(pub_key.size() == pubkey_bytes);
    return import_public_key(pub_key.ubegin());
  }
  bool import_public_key(const ellcurve::TwEdwardsCurve::SegrePoint &Pub_Key);
  bool export_public_key(unsigned char pubkey_buffer[pubkey_bytes]) const;
  bool export_public_key(td::MutableSlice pubk) const {
    CHECK(pubk.size() == pubkey_bytes);
    return export_public_key(pubk.ubegin());
  }
  bool check_message_signature(const unsigned char signature[sign_bytes], const unsigned char *message,
                               std::size_t msg_size);
  bool check_message_signature(td::Slice signature, td::Slice message) {
    CHECK(signature.size() == sign_bytes);
    return check_message_signature(signature.ubegin(), message.ubegin(), message.size());
  }
  void clear();
  bool ok() const {
    return inited == pk_init;
  }
  const unsigned char *get_pubkey_ptr() const {
    return inited == pk_init ? pubkey : 0;
  }
  const ellcurve::TwEdwardsCurve::SegrePoint &get_point() const {
    return PubKey;
  }
  const ellcurve::MontgomeryCurve::PointXZ &get_point_xz() const {
    return PubKey_xz;
  }
};
class PrivateKey {
 public:
  struct priv_key_no_copy {};
  PrivateKey() : inited(false) {
    memset(privkey, 0, privkey_bytes);
  }
  PrivateKey(const unsigned char pk[privkey_bytes]) : inited(false) {
    memset(privkey, 0, privkey_bytes);
    import_private_key(pk);
  }
  PrivateKey(td::Slice pk) : inited(false) {
    CHECK(pk.size() == privkey_bytes);
    memset(privkey, 0, privkey_bytes);
    import_private_key(pk.ubegin());
  }
  ~PrivateKey() {
    clear();
  }
  bool random_private_key(bool strong = false);
  bool import_private_key(const unsigned char pk[privkey_bytes]);
  bool import_private_key(td::Slice pk) {
    CHECK(pk.size() == privkey_bytes);
    return import_private_key(pk.ubegin());
  }
  bool export_private_key(unsigned char pk[privkey_bytes]) const;  // careful!
  bool export_private_key(td::MutableSlice pk) const {             // careful!
    return export_private_key(pk.ubegin());
  }
  bool export_public_key(unsigned char pubk[pubkey_bytes]) const {
    return PubKey.export_public_key(pubk);
  }
  bool export_public_key(td::MutableSlice pubk) const {
    return PubKey.export_public_key(pubk);
  }
  void clear();
  bool ok() const {
    return inited;
  }
  // used for EdDSA (sign)
  bool sign_message(unsigned char signature[sign_bytes], const unsigned char *message, std::size_t msg_size);
  bool sign_message(td::MutableSlice signature, td::Slice message) {
    CHECK(signature.size() == sign_bytes);
    return sign_message(signature.ubegin(), message.ubegin(), message.size());
  }
  // used for ECDH (encrypt / decrypt)
  bool compute_shared_secret(unsigned char secret[shared_secret_bytes], const PublicKey &Pub);
  bool compute_shared_secret(td::MutableSlice secret, const PublicKey &Pub) {
    CHECK(secret.size() == shared_secret_bytes);
    return compute_shared_secret(secret.ubegin(), Pub);
  }
  // used for EC asymmetric decryption
  bool compute_temp_shared_secret(unsigned char secret[shared_secret_bytes],
                                  const unsigned char temp_pub_key[pubkey_bytes]);
  const PublicKey &get_public_key() const {
    return PubKey;
  }
 private:
  bool inited;
  unsigned char privkey[privkey_bytes];
  unsigned char priv_salt[32];
  arith::Bignum priv_exp;
  PublicKey PubKey;
  bool process_private_key();
  PrivateKey(const PrivateKey &) {
    throw priv_key_no_copy();
  }
  PrivateKey &operator=(const PrivateKey &) {
    throw priv_key_no_copy();
  }
};
// use one TempKeyGenerator object a lot of times
class TempKeyGenerator {
  enum { salt_size = 64 };
  unsigned char random_salt[salt_size];
  unsigned char buffer[privkey_bytes];
 public:
  TempKeyGenerator() {
    prng::rand_gen().strong_rand_bytes(random_salt, salt_size);
  }
  ~TempKeyGenerator() {
    memset(random_salt, 0, salt_size);
    memset(buffer, 0, privkey_bytes);
  }
  unsigned char *get_temp_private_key(unsigned char *to, const unsigned char *message, std::size_t size,
                                      const unsigned char *rand = 0, std::size_t rand_size = 0);  // rand may be 0
  void create_temp_private_key(PrivateKey &pk, const unsigned char *message, std::size_t size,
                               const unsigned char *rand = 0, std::size_t rand_size = 0);
  // sets temp_pub_key and shared_secret for one-time asymmetric encryption of message
  bool create_temp_shared_secret(unsigned char temp_pub_key[pubkey_bytes], unsigned char secret[shared_secret_bytes],
                                 const PublicKey &recipientPubKey, const unsigned char *message, std::size_t size,
                                 const unsigned char *rand = 0, std::size_t rand_size = 0);
};
}  // namespace Ed25519
}  // namespace crypto