/*
 * Copyright 2019 Google LLC.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef PRIVATE_JOIN_AND_COMPUTE_EC_COMMUTATIVE_CIPHER_H_
#define PRIVATE_JOIN_AND_COMPUTE_EC_COMMUTATIVE_CIPHER_H_

#include <memory>
#include <string>
#include <utility>

#include "absl/strings/string_view.h"
#include "private_join_and_compute/crypto/big_num.h"
#include "private_join_and_compute/crypto/context.h"
#include "private_join_and_compute/crypto/ec_group.h"
#include "private_join_and_compute/crypto/ec_point.h"
#include "private_join_and_compute/util/status.inc"

namespace private_join_and_compute {

// ECCommutativeCipher class with the property that K1(K2(a)) = K2(K1(a))
// where K(a) is encryption with the key K. https://eprint.iacr.org/2008/356.pdf
//
// This class allows two parties to determine if they share the same value,
// without revealing the sensitive value to each other.
//
// This class also allows homomorphically re-encrypting an ElGamal ciphertext
// with an EC cipher key K. If the original ciphertext was an encryption of m,
// then the re-encrypted ciphertext is effectively an encryption of K(m). This
// re-encryption does not re-randomize the ciphertext, and so is only secure
// when the underlying messages "m" are pseudorandom.
//
// The encryption is performed over an elliptic curve.
//
// This class is not thread-safe.
//
// Security: The provided bit security is half the number of bits of the
//  underlying curve. For example, using curve NID_X9_62_prime256v1 gives 128
//  bit security.
//
// Example: To generate a cipher with a new private key for the named curve
//    NID_X9_62_prime256v1. The key can be securely stored and reused.
//    #include <openssl/obj_mac.h>
//    std::unique_ptr<ECCommutativeCipher> cipher =
//        ECCommutativeCipher::CreateWithNewKey(
//            NID_X9_62_prime256v1, ECCommutativeCipher::HashType::SSWU_RO);
//    string key_bytes = cipher->GetPrivateKeyBytes();
//
//  Example: To generate a cipher with an existing private key for the named
//    curve NID_X9_62_prime256v1.
//    #include <openssl/obj_mac.h>
//    std::unique_ptr<ECCommutativeCipher> cipher =
//        ECCommutativeCipher::CreateFromKey(
//            NID_X9_62_prime256v1, key_bytes,
//            ECCommutativeCipher::HashType::SSWU_RO);
//
// Example: To encrypt a message using a std::unique_ptr<ECCommutativeCipher>
//    cipher generated as above.
//    string encrypted_string = cipher->Encrypt("secret");
//
// Example: To re-encrypt a message already encrypted by another party using a
//    std::unique_ptr<ECCommutativeCipher> cipher generated as above.
//    ::private_join_and_compute::StatusOr<string> double_encrypted_string =
//        cipher->ReEncrypt(encrypted_string);
//
// Example: To decrypt a message that has already been encrypted by the same
//    party using a std::unique_ptr<ECCommutativeCipher> cipher generated as
//    above.
//    ::private_join_and_compute::StatusOr<string> decrypted_string =
//        cipher->Decrypt(encrypted_string);
//
// Example: To re-encrypt a message that has already been encrypted using a
// std::unique_ptr<CommutativeElGamal> ElGamal key:
//    ::private_join_and_compute::StatusOr<std::pair<string, string>>
//    double_encrypted_string =
//        cipher->ReEncryptElGamalCiphertext(elgamal_ciphertext);

class ECCommutativeCipher {
 public:
  // The hash function used by the ECCommutativeCipher in order to hash strings
  // to EC curve points.
  enum HashType {
    SHA256,
    SHA384,
    SHA512,
    SSWU_RO,
  };

  // Check for valid HashType.
  static bool ValidateHashType(HashType hash_type);

  // Check for valid HashType.
  static bool ValidateHashType(int hash_type);

  // ECCommutativeCipher is neither copyable nor assignable.
  ECCommutativeCipher(const ECCommutativeCipher&) = delete;
  ECCommutativeCipher& operator=(const ECCommutativeCipher&) = delete;

  // Creates an ECCommutativeCipher object with a new random private key.
  // Use this method when the key is created for the first time or it needs to
  // be refreshed.
  // Returns INVALID_ARGUMENT status instead if the curve_id is not valid
  // or INTERNAL status when crypto operations are not successful.
  static ::private_join_and_compute::StatusOr<
      std::unique_ptr<ECCommutativeCipher>>
  CreateWithNewKey(int curve_id, HashType hash_type);

  // Creates an ECCommutativeCipher object with the given private key.
  // A new key should be created for each session and all values should be
  // unique in one session because the encryption is deterministic.
  // Use this when the key is stored securely to be used at different steps of
  // the protocol in the same session or by multiple processes.
  // Returns INVALID_ARGUMENT status instead if the private_key is not valid for
  // the given curve or the curve_id is not valid.
  // Returns INTERNAL status when crypto operations are not successful.
  static ::private_join_and_compute::StatusOr<
      std::unique_ptr<ECCommutativeCipher>>
  CreateFromKey(int curve_id, absl::string_view key_bytes, HashType hash_type);

  // Creates an ECCommutativeCipher object with a new private key generated from
  // the given seed and index.  This will deterministically generate a key and
  // this should not be re-used across multiple sessions.  The seed should be a
  // cryptographically strong random string of at least 16 bytes.
  // Returns INTERNAL status when crypto operations are not successful.
  static ::private_join_and_compute::StatusOr<
      std::unique_ptr<ECCommutativeCipher>>
  CreateWithKeyFromSeed(int curve_id, absl::string_view seed_bytes,
                        absl::string_view tag_bytes, HashType hash_type);

  // Encrypts a string with the private key to a point on the elliptic curve.
  //
  // To encrypt, the string is hashed to a point on the curve which is then
  // multiplied with the private key.
  //
  // The resulting point is returned encoded in compressed form as defined in
  // ANSI X9.62 ECDSA.
  //
  // Returns an INVALID_ARGUMENT error code if an error occurs.
  //
  // This method is not threadsafe.
  ::private_join_and_compute::StatusOr<std::string> Encrypt(
      absl::string_view plaintext);

  // Encrypts an encoded point with the private key.
  //
  // Returns an INVALID_ARGUMENT error code if the input is not a valid encoding
  // of a point on this curve as defined in ANSI X9.62 ECDSA.
  //
  // The result is a point encoded in compressed form.
  //
  // This method can also be used to encrypt a value that has already been
  // hashed to the curve.
  //
  // This method is not threadsafe.
  ::private_join_and_compute::StatusOr<std::string> ReEncrypt(
      absl::string_view ciphertext);

  // Encrypts an ElGamal ciphertext with the private key.
  //
  // Returns an INVALID_ARGUMENT error code if the input is not a valid encoding
  // of an ElGamal ciphertext on this curve as defined in ANSI X9.62 ECDSA.
  //
  // The result is another ElGamal ciphertext, encoded in compressed form.
  //
  // This method is not threadsafe.
  ::private_join_and_compute::StatusOr<std::pair<std::string, std::string>>
  ReEncryptElGamalCiphertext(
      const std::pair<std::string, std::string>& elgamal_ciphertext);

  // Decrypts an encoded point with the private key.
  //
  // Returns an INVALID_ARGUMENT error code if the input is not a valid encoding
  // of a point on this curve as defined in ANSI X9.62 ECDSA.
  //
  // The result is a point encoded in compressed form.
  //
  // If the input point was double-encrypted, once with this key and once with
  // another key, then the result point is single-encrypted with the other key.
  //
  // If the input point was single encrypted with this key, then the result
  // point is the original, unencrypted point. Note that this will not reverse
  // hashing to the curve.
  //
  // This method is not threadsafe.
  ::private_join_and_compute::StatusOr<std::string> Decrypt(
      absl::string_view ciphertext);

  // Hashes a string to a point on the elliptic curve using the
  // "try-and-increment" method.
  // See Section 5.2 of https://crypto.stanford.edu/~dabo/papers/bfibe.pdf.
  //
  // The resulting point is returned encoded in compressed form as defined in
  // ANSI X9.62 ECDSA.
  //
  // Returns an INVALID_ARGUMENT error code if an error occurs.
  //
  // This method is not threadsafe.
  ::private_join_and_compute::StatusOr<std::string> HashToTheCurve(
      absl::string_view plaintext);

  // Returns the private key bytes so the key can be stored and reused.
  std::string GetPrivateKeyBytes() const;

 private:
  // Creates a new ECCommutativeCipher object with the given private key for
  // the given EC group.
  ECCommutativeCipher(std::unique_ptr<Context> context, ECGroup group,
                      BigNum private_key, HashType hash_type);

  // Encrypts a point by multiplying the point with the private key.
  ::private_join_and_compute::StatusOr<ECPoint> Encrypt(const ECPoint& point);

  // Hashes a string to a point on the elliptic curve.
  ::private_join_and_compute::StatusOr<ECPoint> HashToTheCurveInternal(
      absl::string_view plaintext);

  // Context used for storing temporary values to be reused across openssl
  // function calls for better performance.
  std::unique_ptr<Context> context_;

  // The EC Group representing the curve definition.
  const ECGroup group_;

  // The private key used for encryption.
  const BigNum private_key_;

  // The private key inverse, used for decryption.
  const BigNum private_key_inverse_;

  // The hash function used by the cipher.
  const HashType hash_type_;
};

}  // namespace private_join_and_compute

#endif  // PRIVATE_JOIN_AND_COMPUTE_EC_COMMUTATIVE_CIPHER_H_
