import { AbstractSymmetricKeyGenerator } from '../interface';

/**
 * RFC5869 - https://tools.ietf.org/html/rfc5869

    HKDF consists of two phases with separate goals. They must be used properly!
        - Extract phase:
          Create a PRK from the IKM (key) using the salt. The salt is valuable to prevent an attacker from
          searching multiple derived keys in rainbow tables at once. This extraction can be skipped if the IKM
          is already a cryptographically secure random and may be used directly as PRK.
          Do *not* extract more PRKs from the same IKM with different salts. This is not the intended usage and
          is not cryptographically analysed for security!
        - Expand phase:
          Create a lot (as many as necessary) practically independent cryptographically usable keys from PRK.
          The same PRK can be used many times with different 'info' to separate the keys.

    In practise the extract-expand constructs are commonly fused together. This is not a problem, but skipping
    the first phase is out of the question, and special care must be taken to always use the same salt for the
    same IKM to get the same PRK to re-generate the keys or generate new keys for a different context (info).

    The motivation to use HKDF is from using AES. For AES to be used safely, the keys have to be changed
    periodically. By creating practically independent keys with HKDF we avoid the birthday paradox and various
    safety limits on CMAC, nonce pool, etc.

    Usage cases:
        - P2P messages with ECDH shared secret:
          The shared secret is suitable to be used as IKM.
          For salt use the ordered account ids. Those will be trust-verified to be accurate before use of the EC
          keys, so an attacker won't be able to manipulate them.
          To generate new keys periodically the 'info' shall include the intended use-context in some form
          along with a timestamp of the target time-window. For use-cases where a limited number of messages
          will be sent between the participants (technical data-exchange for example), the timestamp can be omitted.
        - Group messages with a pre-shared secret:
          Use the same solution as for P2P. The salt shall be the id of the group.

    Keep in mind:
        IKM (key):
            - must be high entropy of sufficient length
            - multiple IKMs must be independent of each other
        salt:
            - ideally random or pseudorandom, but a low entropy value is still significantly useful
            - no need to keep it a secret
            - the same salt can be used with multiple IKM values
            - the salt must be independent of the IKMs
            - salt values must not be chosen or manipulated by an attacker
        The same IKM must be used with the same salt consistently.
        info:
            - must be independent of the IKMs
            - define different usage contexts or time-periods to create new/more keys from the same PRK

    Salting properly:
        We wish to have unique salts for the different IKMs and ideally they should also be of high quality entropy.
        High quality entropy is not always available as-is or if it is available, it would need to be stored along
        with the key to be able to derive sub-keys deterministically. Luckily the uniqueness of the salt is easy to
        provide with information about the context. Combining that with pre-defined high quality random values will
        suffice both of our requirements to create perfect salts. This also limits the ability of an attacker to
        choose a salt, even if they have some decision in it.

    NOTE: This class is mostly here to group the notes to the HKDF configurations in general.
 */
export abstract class AbstractHKDFKeyGenerator extends AbstractSymmetricKeyGenerator {
  /**
     * b=b"\x8a\xb5C}gG\xe3,\xedo\xf6\x03\xa9^/y\x86\x0e\xa2\xc0\xa7\xfdI\x8b(\xda\xaf\x9f\x07\x000\xf7"
        print([i for i in b])
     */
  // static salts to use with low-entropy values to achieve uniqueness and quality at the same time
  public static SALTS = [
    new Uint8Array([
      77, 73, 155, 195, 110, 168, 77, 130, 155, 180, 233, 207, 99, 217, 171, 244, 255, 246, 206, 82,
      74, 86, 215, 244, 210, 77, 15, 54, 6, 186, 179, 19,
    ]),
    new Uint8Array([
      187, 169, 156, 35, 101, 150, 123, 9, 70, 62, 234, 221, 24, 90, 133, 170, 40, 158, 149, 49,
      192, 241, 252, 151, 220, 38, 225, 123, 226, 165, 138, 245,
    ]),
    new Uint8Array([
      55, 237, 94, 134, 13, 139, 190, 144, 25, 203, 120, 3, 53, 239, 147, 232, 175, 25, 110, 37,
      224, 73, 33, 234, 176, 93, 42, 61, 213, 188, 207, 101,
    ]),
    new Uint8Array([
      138, 181, 67, 125, 103, 71, 227, 44, 237, 111, 246, 3, 169, 94, 47, 121, 134, 14, 162, 192,
      167, 253, 73, 139, 40, 218, 175, 159, 7, 0, 48, 247,
    ]),
  ];

  //public static abstract new_key(length: number, key: Uint8Array, salt?: Uint8Array, info?: Uint8Array): Uint8Array

  //public static abstract describe(): string
}

/**
 * Uses SHA256 which has the best hardware acceleration currently.
 */
export class HKDFKeyGeneratorV1 extends AbstractHKDFKeyGenerator {
  public static new_key(
    length: number,
    key: Uint8Array,
    salt: Uint8Array = new Uint8Array([]),
    info: Uint8Array = new Uint8Array([])
  ): Promise<Uint8Array> {
    let algo: HkdfParams;
    algo = {
      name: 'HKDF',
      hash: 'SHA-256',
      salt,
      info,
    };

    return crypto.subtle
      .importKey('raw', key, { name: 'HKDF' }, false, ['deriveBits'])
      .then((cryptoKey) => {
        return crypto.subtle.deriveBits(algo, cryptoKey, length * 8).then((deriveBits) => {
          return new Uint8Array(deriveBits);
        });
      });
  }

  public static describe(): string {
    return 'HKDF-SHA256';
  }
}

/**
 * SHA512
 */
export class HKDFKeyGeneratorV2 extends AbstractHKDFKeyGenerator {
  public static new_key(
    length: number,
    key: Uint8Array,
    salt: Uint8Array = new Uint8Array([]),
    info: Uint8Array = new Uint8Array([])
  ): Promise<Uint8Array> {
    let algo: HkdfParams;
    algo = {
      name: 'HKDF',
      hash: 'SHA-512',
      salt,
      info,
    };

    return crypto.subtle
      .importKey('raw', key, { name: 'HKDF' }, false, ['deriveBits'])
      .then((cryptoKey) => {
        return crypto.subtle.deriveBits(algo, cryptoKey, length * 8).then((deriveBits) => {
          return new Uint8Array(deriveBits);
        });
      });
  }

  public static describe(): string {
    return 'HKDF-SHA512';
  }
}
