// Copyright 2017 Google Inc.
//
// 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.
//
////////////////////////////////////////////////////////////////////////////////

package com.google.crypto.tink.subtle;

import com.google.crypto.tink.annotations.Alpha;
import com.google.crypto.tink.internal.Curve25519;
import com.google.crypto.tink.internal.Field25519;
import java.security.InvalidKeyException;
import java.util.Arrays;

/**
 * Defines <a href="https://cr.yp.to/ecdh/curve25519-20060209.pdf">the ECDH Curve25519 function</a>,
 * also known as the X25519 function.
 *
 * <p>This implementation is based on <a
 * href="https://github.com/agl/curve25519-donna/blob/master/curve25519-donna.c">curve255-donna</a>.
 *
 * <h3>Warning</h3>
 *
 * <p>Do not use this API or any other APIs including fields and methods marked with the @Alpha
 * annotation. They can be modified in any way, or even removed, at any time. They are in the
 * package, but not for official, production release, but only for testing.
 *
 * <h3>Usage</h3>
 *
 * <pre>
 * Alice:
 * byte[] privateKeyA = X25519.generatePrivateKey();
 * byte[] publicKeyA = X25519.publicFromPrivate(privateKeyA);
 * Bob:
 * byte[] privateKeyB = X25519.generatePrivateKey();
 * byte[] publicKeyB = X25519.publicFromPrivate(privateKeyB);
 *
 * Alice sends publicKeyA to Bob and Bob sends publicKeyB to Alice.
 * Alice:
 * byte[] sharedSecretA = X25519.computeSharedSecret(privateKeyA, publicKeyB);
 * Bob:
 * byte[] sharedSecretB = X25519.computeSharedSecret(privateKeyB, publicKeyA);
 * such that sharedSecretA == sharedSecretB.
 * </pre>
 */
@Alpha
public final class X25519 {
  /**
   * Returns a 32-byte private key for Curve25519.
   *
   * <p>Note from BoringSSL: All X25519 implementations should decode scalars correctly (see
   * https://tools.ietf.org/html/rfc7748#section-5). However, if an implementation doesn't then it
   * might interoperate with random keys a fraction of the time because they'll, randomly, happen to
   * be correctly formed.
   *
   * <p>Thus we do the opposite of the masking here to make sure that our private keys are never
   * correctly masked and so, hopefully, any incorrect implementations are deterministically broken.
   *
   * <p>This does not affect security because, although we're throwing away entropy, a valid
   * implementation of computeSharedSecret should throw away the exact same bits anyway.
   */
  @SuppressWarnings("NarrowingCompoundAssignment")
  public static byte[] generatePrivateKey() {
    byte[] privateKey = Random.randBytes(Field25519.FIELD_LEN);

    privateKey[0] |= 7;
    privateKey[31] &= 63;
    privateKey[31] |= 128;

    return privateKey;
  }

  /**
   * Returns the 32-byte shared key (i.e., privateKey·peersPublicValue on the curve).
   *
   * @param privateKey 32-byte private key
   * @param peersPublicValue 32-byte public value
   * @return the 32-byte shared key
   * @throws InvalidKeyException when {@code privateKey} is not 32-byte or {@code peersPublicValue}
   *     is invalid.
   */
  @SuppressWarnings("NarrowingCompoundAssignment")
  public static byte[] computeSharedSecret(byte[] privateKey, byte[] peersPublicValue)
      throws InvalidKeyException {
    if (privateKey.length != Field25519.FIELD_LEN) {
      throw new InvalidKeyException("Private key must have 32 bytes.");
    }
    long[] x = new long[Field25519.LIMB_CNT + 1];

    byte[] e = Arrays.copyOf(privateKey, Field25519.FIELD_LEN);
    e[0] &= 248;
    e[31] &= 127;
    e[31] |= 64;

    Curve25519.curveMult(x, e, peersPublicValue);
    return Field25519.contract(x);
  }

  /**
   * Returns the 32-byte Diffie-Hellman public value based on the given {@code privateKey} (i.e.,
   * {@code privateKey}·[9] on the curve).
   *
   * @param privateKey 32-byte private key
   * @return 32-byte Diffie-Hellman public value
   * @throws InvalidKeyException when the {@code privateKey} is not 32 bytes.
   */
  public static byte[] publicFromPrivate(byte[] privateKey) throws InvalidKeyException {
    if (privateKey.length != Field25519.FIELD_LEN) {
      throw new InvalidKeyException("Private key must have 32 bytes.");
    }
    byte[] base = new byte[Field25519.FIELD_LEN];
    base[0] = 9;
    return computeSharedSecret(privateKey, base);
  }

  private X25519() {}
}
