// Copyright 2021 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
//
//     http://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 TINK_INTERNAL_EC_UTIL_H_
#define TINK_INTERNAL_EC_UTIL_H_

#include <stdint.h>

#include <memory>
#include <string>

#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "openssl/ec.h"
#include "tink/internal/ssl_unique_ptr.h"
#include "tink/secret_data.h"
#include "tink/subtle/common_enums.h"

namespace crypto {
namespace tink {
namespace internal {

constexpr int64_t X25519KeyPubKeySize() { return 32; }
constexpr int64_t X25519KeyPrivKeySize() { return 32; }
constexpr int64_t X25519KeySharedKeySize() { return 32; }

constexpr int64_t Ed25519KeyPubKeySize() { return 32; }
constexpr int64_t Ed25519KeyPrivKeySize() { return 32; }

struct EcKey {
  subtle::EllipticCurveType curve;
  // Affine coordinates in bigendian representation.
  std::string pub_x;
  std::string pub_y;
  // Big integer in bigendian representation.
  SecretData priv;
};

struct X25519Key {
  uint8_t public_value[X25519KeyPubKeySize()];
  SecretData private_key = SecretData(X25519KeyPrivKeySize());
};

struct Ed25519Key {
  std::string public_key;
  SecretData private_key;
};

// EcKey.

// Returns a new EC key for the specified curve.
absl::StatusOr<EcKey> NewEcKey(
    crypto::tink::subtle::EllipticCurveType curve_type);

// Returns a new EC key for the specified curve derived from a secret seed.
absl::StatusOr<EcKey> NewEcKey(
    crypto::tink::subtle::EllipticCurveType curve_type,
    const SecretData &secret_seed);

// X25519Key Utils.

// Returns a new X25519Key key. It returns a kInternal error status if the
// OpenSSL/BoringSSL APIs fail.
absl::StatusOr<std::unique_ptr<X25519Key>> NewX25519Key();

// Given an OpenSSL/BoringSSL key EC_KEY `key` and curve type `curve` return an
// EcKey.
absl::StatusOr<EcKey> EcKeyFromSslEcKey(
    crypto::tink::subtle::EllipticCurveType curve, const EC_KEY &key);

// Generates a shared secret using `private_key` and `peer_public_key`; keys
// must be X25519 keys otherwise an error is returned.
absl::StatusOr<SecretData> ComputeX25519SharedSecret(EVP_PKEY *private_key,
                                                     EVP_PKEY *peer_public_key);

// Computes the corresponding public+private key for the supplied private key.
absl::StatusOr<std::unique_ptr<X25519Key>> X25519KeyFromPrivateKey(
    const SecretData &private_key);

// Ed25519Key Utils.

// Returns a new ED25519 key.
absl::StatusOr<std::unique_ptr<Ed25519Key>> NewEd25519Key();

// Returns a new ED25519 key generated from a 32-byte secret seed.
absl::StatusOr<std::unique_ptr<Ed25519Key>> NewEd25519Key(
    const SecretData &secret_seed);

// EC_POINT Encode/Decode.

// Given x, y as curve_size_in_bytes big-endian byte array, encoding is as
// follows:
// - The uncompressed point is encoded as 0x04 || x || y.
// - The compressed point is encoded as:
//   - 0x03 || x if the least significant bit of y is 1;
//   - 0x02 || x otherwise.

// Returns OpenSSL/BoringSSL's EC_POINT constructed from curve type
// `curve_type`, point `format` and encoded public key's point `encoded_point`.
absl::StatusOr<SslUniquePtr<EC_POINT>> EcPointDecode(
    crypto::tink::subtle::EllipticCurveType curve_type,
    crypto::tink::subtle::EcPointFormat format,
    absl::string_view encoded_point);

// Returns the encoded public key based on curve type `curve_type`, point
// `format` and OpenSSL/BoringSSL's EC_POINT public key `point`.
absl::StatusOr<std::string> EcPointEncode(
    crypto::tink::subtle::EllipticCurveType curve_type,
    crypto::tink::subtle::EcPointFormat format, const EC_POINT *point);

// Returns the encoding size of a point on the specified elliptic curve
// `curve_type` when the given point `format` is used.
absl::StatusOr<int32_t> EcPointEncodingSizeInBytes(
    crypto::tink::subtle::EllipticCurveType curve_type,
    crypto::tink::subtle::EcPointFormat format);

// Returns the size (in bytes) of an element of the field over which
// the curve `curve_type` is defined.
absl::StatusOr<int32_t> EcFieldSizeInBytes(
    crypto::tink::subtle::EllipticCurveType curve_type);

// EC_GROUP Utils.

// Returns OpenSSL/BoringSSL's EC_GROUP constructed from the given `curve_type`.
absl::StatusOr<SslUniquePtr<EC_GROUP>> EcGroupFromCurveType(
    crypto::tink::subtle::EllipticCurveType curve_type);

// Returns the curve type associated with the given `group`.
absl::StatusOr<crypto::tink::subtle::EllipticCurveType> CurveTypeFromEcGroup(
    const EC_GROUP *group);

// Returns OpenSSL/BoringSSL's EC_POINT constructed from the curve type,
// big-endian representation of public key's x-coordinate and y-coordinate.
absl::StatusOr<SslUniquePtr<EC_POINT>> GetEcPoint(
    crypto::tink::subtle::EllipticCurveType curve, absl::string_view pubx,
    absl::string_view puby);

// Transforms ECDSA IEEE_P1363 signature encoding to DER encoding.
//
// The IEEE_P1363 signature's format is r || s, where r and s are zero-padded
// and have the same size in bytes as the order of the curve. For example, for
// NIST P-256 curve, r and s are zero-padded to 32 bytes.
//
// The DER signature is encoded using ASN.1
// (https://tools.ietf.org/html/rfc5480#appendix-A):
//   ECDSA-Sig-Value :: = SEQUENCE { r INTEGER, s INTEGER }.
// In particular, the encoding is:
//   0x30 || totalLength || 0x02 || r's length || r || 0x02 || s's length || s
absl::StatusOr<std::string> EcSignatureIeeeToDer(const EC_GROUP *group,
                                                 absl::string_view ieee_sig);

// Returns the ECDH's shared secret between two peers A and B using A's private
// key `priv_key` and B's public key `pub_key`. Returns error if `pub_key`
// is not on `priv_key`'s curve `curve`.
absl::StatusOr<SecretData> ComputeEcdhSharedSecret(
    crypto::tink::subtle::EllipticCurveType curve, const BIGNUM *priv_key,
    const EC_POINT *pub_key);

}  // namespace internal
}  // namespace tink
}  // namespace crypto

#endif  // TINK_INTERNAL_EC_UTIL_H_
