/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {SecurityException} from '../exception/security_exception';

import * as Bytes from './bytes';
import * as EllipticCurves from './elliptic_curves';
import * as Hkdf from './hkdf';

/**
 * HKDF-based ECIES-KEM (key encapsulation mechanism) for ECIES recipient.
 */
export class EciesHkdfKemRecipient {
  private readonly privateKey_: CryptoKey;

  constructor(privateKey: CryptoKey) {
    if (!privateKey) {
      throw new SecurityException('Private key has to be non-null.');
    }

    // CryptoKey should have the properties type and algorithm.
    if (privateKey.type !== 'private' || !privateKey.algorithm) {
      throw new SecurityException('Expected crypto key of type: private.');
    }
    this.privateKey_ = privateKey;
  }

  /**
   * @param kemToken the public ephemeral point.
   * @param keySizeInBytes The length of the generated pseudorandom
   *     string in bytes. The maximal size is 255 * DigestSize, where DigestSize
   *     is the size of the underlying HMAC.
   * @param pointFormat The format of the
   *     public ephemeral point.
   * @param hkdfHash the name of the hash function. Accepted names are
   *     SHA-1, SHA-256 and SHA-512.
   * @param hkdfInfo Context and application specific
   *     information (can be a zero-length array).
   * @param opt_hkdfSalt Salt value (a non-secret random
   *     value). If not provided, it is set to a string of hash length zeros.
   * @return The KEM key and token.
   */
  async decapsulate(
      kemToken: Uint8Array, keySizeInBytes: number,
      pointFormat: EllipticCurves.PointFormatType, hkdfHash: string,
      hkdfInfo: Uint8Array, opt_hkdfSalt?: Uint8Array): Promise<Uint8Array> {
    const {namedCurve}: Partial<EcKeyAlgorithm> = this.privateKey_.algorithm;
    if (!namedCurve) {
      throw new SecurityException('Curve has to be defined.');
    }
    const jwk = EllipticCurves.pointDecode(namedCurve, pointFormat, kemToken);
    const publicKey = await EllipticCurves.importPublicKey('ECDH', jwk);
    const sharedSecret = await EllipticCurves.computeEcdhSharedSecret(
        this.privateKey_, publicKey);
    const hkdfIkm = Bytes.concat(kemToken, sharedSecret);
    const kemKey = await Hkdf.compute(
        keySizeInBytes, hkdfHash, hkdfIkm, hkdfInfo, opt_hkdfSalt);
    return kemKey;
  }
}

export async function fromJsonWebKey(jwk: JsonWebKey):
    Promise<EciesHkdfKemRecipient> {
  const privateKey = await EllipticCurves.importPrivateKey('ECDH', jwk);
  return new EciesHkdfKemRecipient(privateKey);
}
