// 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 static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;

import com.google.crypto.tink.testing.TestUtil;
import com.google.crypto.tink.testing.WycheproofTestUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.X509EncodedKeySpec;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Unit tests for {@link com.google.crypto.tink.subtle.EllipticCurves}. */
@RunWith(JUnit4.class)
public class EllipticCurvesTest {
  // The tests are from
  // http://google.github.io/end-to-end/api/source/src/javascript/crypto/e2e/ecc/ecdh_testdata.js.src.html.

  /**
   * A class for storing test vectors. This class contains the directory for the public and private
   * key, the message and the corresponding signature.
   */
  protected static class TestVector2 {
    protected EllipticCurves.CurveType curve;
    protected EllipticCurves.PointFormatType format;
    protected byte[] encoded;
    BigInteger x;
    BigInteger y;

    protected TestVector2(
        EllipticCurves.CurveType curve,
        EllipticCurves.PointFormatType format,
        String encodedHex,
        String x,
        String y) {
      this.curve = curve;
      this.format = format;
      this.encoded = Hex.decode(encodedHex);
      this.x = new BigInteger(x);
      this.y = new BigInteger(y);
    }
  }

  protected static final TestVector2[] testVectors2 = {
    // NIST_P256
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "04"
            + "b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a"
            + "1886ccdca5487a6772f9401888203f90587cc00a730e2b83d5c6f89b3b568df7",
        "79974177209371530366349631093481213364328002500948308276357601809416549347930",
        "11093679777528052772423074391650378811758820120351664471899251711300542565879"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED,
        "b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a"
            + "1886ccdca5487a6772f9401888203f90587cc00a730e2b83d5c6f89b3b568df7",
        "79974177209371530366349631093481213364328002500948308276357601809416549347930",
        "11093679777528052772423074391650378811758820120351664471899251711300542565879"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.COMPRESSED,
        "03b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a",
        "79974177209371530366349631093481213364328002500948308276357601809416549347930",
        "11093679777528052772423074391650378811758820120351664471899251711300542565879"),
    // Exceptional point: x==0
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "04"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4",
        "0",
        "46263761741508638697010950048709651021688891777877937875096931459006746039284"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED,
        "0000000000000000000000000000000000000000000000000000000000000000"
            + "66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f4",
        "0",
        "46263761741508638697010950048709651021688891777877937875096931459006746039284"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.COMPRESSED,
        "020000000000000000000000000000000000000000000000000000000000000000",
        "0",
        "46263761741508638697010950048709651021688891777877937875096931459006746039284"),
    // Exceptional point: x==-3
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "04"
            + "ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"
            + "19719bebf6aea13f25c96dfd7c71f5225d4c8fc09eb5a0ab9f39e9178e55c121",
        "115792089210356248762697446949407573530086143415290314195533631308867097853948",
        "11508551065151498768481026661199445482476508121209842448718573150489103679777"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED,
        "ffffffff00000001000000000000000000000000fffffffffffffffffffffffc"
            + "19719bebf6aea13f25c96dfd7c71f5225d4c8fc09eb5a0ab9f39e9178e55c121",
        "115792089210356248762697446949407573530086143415290314195533631308867097853948",
        "11508551065151498768481026661199445482476508121209842448718573150489103679777"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P256,
        EllipticCurves.PointFormatType.COMPRESSED,
        "03ffffffff00000001000000000000000000000000fffffffffffffffffffffffc",
        "115792089210356248762697446949407573530086143415290314195533631308867097853948",
        "11508551065151498768481026661199445482476508121209842448718573150489103679777"),
    // NIST_P384
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "04aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a"
            + "385502f25dbf55296c3a545e3872760ab73617de4a96262c6f5d9e98bf9292dc"
            + "29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e"
            + "5f",
        "2624703509579968926862315674456698189185292349110921338781561590"
            + "0925518854738050089022388053975719786650872476732087",
        "8325710961489029985546751289520108179287853048861315594709205902"
            + "480503199884419224438643760392947333078086511627871"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.COMPRESSED,
        "03aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a"
            + "385502f25dbf55296c3a545e3872760ab7",
        "2624703509579968926862315674456698189185292349110921338781561590"
            + "0925518854738050089022388053975719786650872476732087",
        "8325710961489029985546751289520108179287853048861315594709205902"
            + "480503199884419224438643760392947333078086511627871"),
    // x = 0
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400000000000000000000000000000000000000000000000000000000000000"
            + "00000000000000000000000000000000003cf99ef04f51a5ea630ba3f9f960dd"
            + "593a14c9be39fd2bd215d3b4b08aaaf86bbf927f2c46e52ab06fb742b8850e52"
            + "1e",
        "0",
        "9384923975005507693384933751151973636103286582194273515051780595"
            + "652610803541482195894618304099771370981414591681054"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0200000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000",
        "0",
        "9384923975005507693384933751151973636103286582194273515051780595"
            + "652610803541482195894618304099771370981414591681054"),
    // x = 2
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000002732152442fb6ee5c3e6ce1d920c059"
            + "bc623563814d79042b903ce60f1d4487fccd450a86da03f3e6ed525d02017bfd"
            + "b3",
        "2",
        "1772015366480916228638409476801818679957736647795608728422858375"
            + "4887974043472116432532980617621641492831213601947059"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0300000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000002",
        "2",
        "1772015366480916228638409476801818679957736647795608728422858375"
            + "4887974043472116432532980617621641492831213601947059"),
    // x = -3
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "04ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "feffffffff0000000000000000fffffffc2de9de09a95b74e6b2c430363e1afb"
            + "8dff7164987a8cfe0a0d5139250ac02f797f81092a9bdc0e09b574a8f43bf80c"
            + "17",
        "3940200619639447921227904010014361380507973927046544666794829340"
            + "4245721771496870329047266088258938001861606973112316",
        "7066741234775658874139271223692271325950306561732202191471600407"
            + "582071247913794644254895122656050391930754095909911"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P384,
        EllipticCurves.PointFormatType.COMPRESSED,
        "03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "feffffffff0000000000000000fffffffc",
        "3940200619639447921227904010014361380507973927046544666794829340"
            + "4245721771496870329047266088258938001861606973112316",
        "7066741234775658874139271223692271325950306561732202191471600407"
            + "582071247913794644254895122656050391930754095909911"),
    // NIST_P521
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b"
            + "4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2"
            + "e5bd66011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd"
            + "17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94"
            + "769fd16650",
        "2661740802050217063228768716723360960729859168756973147706671368"
            + "4188029449964278084915450806277719023520942412250655586621571135"
            + "45570916814161637315895999846",
        "3757180025770020463545507224491183603594455134769762486694567779"
            + "6155444774405563166912344050129455395621444445372894285225856667"
            + "29196580810124344277578376784"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0200c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b"
            + "4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2"
            + "e5bd66",
        "2661740802050217063228768716723360960729859168756973147706671368"
            + "4188029449964278084915450806277719023520942412250655586621571135"
            + "45570916814161637315895999846",
        "3757180025770020463545507224491183603594455134769762486694567779"
            + "6155444774405563166912344050129455395621444445372894285225856667"
            + "29196580810124344277578376784"),
    // x = 0
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "00000000d20ec9fea6b577c10d26ca1bb446f40b299e648b1ad508aad068896f"
            + "ee3f8e614bc63054d5772bf01a65d412e0bcaa8e965d2f5d332d7f39f846d440"
            + "ae001f4f87",
        "0",
        "2816414230262626695230339754503506208598534788872316917808418392"
            + "0894686826982898181454171638541149642517061885689521392260532032"
            + "30035588176689756661142736775"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0300000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "000000",
        "0",
        "2816414230262626695230339754503506208598534788872316917808418392"
            + "0894686826982898181454171638541149642517061885689521392260532032"
            + "30035588176689756661142736775"),
    // x = 1
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "0000010010e59be93c4f269c0269c79e2afd65d6aeaa9b701eacc194fb3ee03d"
            + "f47849bf550ec636ebee0ddd4a16f1cd9406605af38f584567770e3f272d688c"
            + "832e843564",
        "1",
        "2265505274322546447629271557184988697103589068170534253193208655"
            + "0778100463909972583865730916407864371153050622267306901033104806"
            + "9570407113457901669103973732"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0200000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "000001",
        "1",
        "2265505274322546447629271557184988697103589068170534253193208655"
            + "0778100463909972583865730916407864371153050622267306901033104806"
            + "9570407113457901669103973732"),
    // x = 2
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0400000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "00000200d9254fdf800496acb33790b103c5ee9fac12832fe546c632225b0f7f"
            + "ce3da4574b1a879b623d722fa8fc34d5fc2a8731aad691a9a8bb8b554c95a051"
            + "d6aa505acf",
        "2",
        "2911448509017565583245824537994174021964465504209366849707937264"
            + "0417919148200722009442607963590225526059407040161685364728526719"
            + "10134103604091376779754756815"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0300000000000000000000000000000000000000000000000000000000000000"
            + "0000000000000000000000000000000000000000000000000000000000000000"
            + "000002",
        "2",
        "2911448509017565583245824537994174021964465504209366849707937264"
            + "0417919148200722009442607963590225526059407040161685364728526719"
            + "10134103604091376779754756815"),
    // x = -2
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.UNCOMPRESSED,
        "0401ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "fffffd0010e59be93c4f269c0269c79e2afd65d6aeaa9b701eacc194fb3ee03d"
            + "f47849bf550ec636ebee0ddd4a16f1cd9406605af38f584567770e3f272d688c"
            + "832e843564",
        "6864797660130609714981900799081393217269435300143305409394463459"
            + "1855431833976560521225596406614545549772963113914808580371219879"
            + "99716643812574028291115057149",
        "2265505274322546447629271557184988697103589068170534253193208655"
            + "0778100463909972583865730916407864371153050622267306901033104806"
            + "9570407113457901669103973732"),
    new TestVector2(
        EllipticCurves.CurveType.NIST_P521,
        EllipticCurves.PointFormatType.COMPRESSED,
        "0201ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
            + "fffffd",
        "6864797660130609714981900799081393217269435300143305409394463459"
            + "1855431833976560521225596406614545549772963113914808580371219879"
            + "99716643812574028291115057149",
        "2265505274322546447629271557184988697103589068170534253193208655"
            + "0778100463909972583865730916407864371153050622267306901033104806"
            + "9570407113457901669103973732"),
  };

  @Test
  public void testFieldSizeInBytes() throws Exception {
    assertThat(
            EllipticCurves.fieldSizeInBytes(
                EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256).getCurve()))
        .isEqualTo(32);
    assertThat(
            EllipticCurves.fieldSizeInBytes(
                EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P384).getCurve()))
        .isEqualTo(48);
    assertThat(
            EllipticCurves.fieldSizeInBytes(
                EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P521).getCurve()))
        .isEqualTo(66);
  }

  @Test
  public void testPointDecode() throws Exception {
    for (TestVector2 test : testVectors2) {
      EllipticCurve curve = EllipticCurves.getCurveSpec(test.curve).getCurve();
      ECPoint p = EllipticCurves.pointDecode(curve, test.format, test.encoded);
      assertEquals(p.getAffineX(), test.x);
      assertEquals(p.getAffineY(), test.y);
    }
  }

  @Test
  public void testPointEncode() throws Exception {
    for (TestVector2 test : testVectors2) {
      EllipticCurve curve = EllipticCurves.getCurveSpec(test.curve).getCurve();
      ECPoint p = new ECPoint(test.x, test.y);
      byte[] encoded = EllipticCurves.pointEncode(curve, test.format, p);
      assertEquals(Hex.encode(encoded), Hex.encode(test.encoded));
    }
  }

  @Test
  public void pointEncode_failsIfPointIsNotOnCurve() throws Exception {
    // Same an entry of testVectors2, but the value of y has been incremented by 1.
    BigInteger x = new BigInteger(
        "79974177209371530366349631093481213364328002500948308276357601809416549347930");
    BigInteger y = new BigInteger(
           "11093679777528052772423074391650378811758820120351664471899251711300542565880");
    // Adding one to y make the point not be on the curve.
    assertThrows(
        GeneralSecurityException.class,
        () ->
            EllipticCurves.pointEncode(
                EllipticCurves.CurveType.NIST_P256,
                EllipticCurves.PointFormatType.UNCOMPRESSED,
                new ECPoint(x, y)));
  }

  @Test
  public void pointDecode_uncompressed_failsIfPointIsNotOnCurve() throws Exception {
    // Same an entry of testVectors2, but the last byte is changed from f7 to f6
    byte[] encoded =
        Hex.decode(
            "04"
                + "b0cfc7bc02fc980d858077552947ffb449b10df8949dee4e56fe21e016dcb25a"
                + "1886ccdca5487a6772f9401888203f90587cc00a730e2b83d5c6f89b3b568df6");
    // Adding one to y make the point not be on the curve.
    assertThrows(GeneralSecurityException.class,
        () -> EllipticCurves.pointDecode(EllipticCurves.CurveType.NIST_P256,
            EllipticCurves.PointFormatType.UNCOMPRESSED, encoded));
  }

  @Test
  public void pointDecode_crunchy_failsIfPointIsNotOnCurve() throws Exception {
    // Same as an entry of testVectors2, but the last byte is changed from f4 to f5
    byte[] encoded =
        Hex.decode(
            "0000000000000000000000000000000000000000000000000000000000000000"
                + "66485c780e2f83d72433bd5d84a06bb6541c2af31dae871728bf856a174f93f5");
    // Adding one to y make the point not be on the curve.
    assertThrows(GeneralSecurityException.class,
        () -> EllipticCurves.pointDecode(EllipticCurves.CurveType.NIST_P256,
            EllipticCurves.PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED, encoded));
  }

  @Test
  public void pointDecode_compressed_failsIfEncodingIsInvalid() throws Exception {
    // Same as an entry of testVectors2, but the last byte is changed from 00 to 01
    byte[] encoded =
        Hex.decode("020000000000000000000000000000000000000000000000000000000000000001");
    assertThrows(GeneralSecurityException.class,
        () -> EllipticCurves.pointDecode(EllipticCurves.CurveType.NIST_P256,
            EllipticCurves.PointFormatType.COMPRESSED, encoded));
  }

  /** A class to store a pair of valid Ecdsa signature in IEEE_P1363 and DER format. */
  protected static class EcdsaIeeeDer {
    public String hexIeee;
    public String hexDer;

    protected EcdsaIeeeDer(String hexIeee, String hexDer) {
      this.hexIeee = hexIeee;
      this.hexDer = hexDer;
    }
  };

  protected static final EcdsaIeeeDer[] ieeeDerTestVector =
      new EcdsaIeeeDer[] {
        new EcdsaIeeeDer( // normal case, short-form length
            "0102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10",
            "302402100102030405060708090a0b0c0d0e0f1002100102030405060708090a0b0c0d0e0f10"),
        new EcdsaIeeeDer( // normal case, long-form length
            "010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000203010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000203",
            "30818802420100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000002030242010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000203"),
        new EcdsaIeeeDer( // zero prefix.
            "0002030405060708090a0b0c0d0e0f100002030405060708090a0b0c0d0e0f10",
            "3022020f02030405060708090a0b0c0d0e0f10020f02030405060708090a0b0c0d0e0f10"),
        new EcdsaIeeeDer( // highest bit is set.
            "00ff030405060708090a0b0c0d0e0f1000ff030405060708090a0b0c0d0e0f10",
            "3024021000ff030405060708090a0b0c0d0e0f10021000ff030405060708090a0b0c0d0e0f10"),
        new EcdsaIeeeDer( // highest bit is set, full length.
            "ff02030405060708090a0b0c0d0e0f10ff02030405060708090a0b0c0d0e0f10",
            "3026021100ff02030405060708090a0b0c0d0e0f10021100ff02030405060708090a0b0c0d0e0f10"),
        new EcdsaIeeeDer( // all zeros.
            "0000000000000000000000000000000000000000000000000000000000000000", "3006020100020100"),
      };

  @Test
  public void testEcdsaIeee2Der() throws Exception {
    for (EcdsaIeeeDer test : ieeeDerTestVector) {
      assertArrayEquals(
          Hex.decode(test.hexDer), EllipticCurves.ecdsaIeee2Der(Hex.decode(test.hexIeee)));
    }
  }

  @Test
  public void testEcdsaDer2Ieee() throws Exception {
    for (EcdsaIeeeDer test : ieeeDerTestVector) {
      assertArrayEquals(
          Hex.decode(test.hexIeee),
          EllipticCurves.ecdsaDer2Ieee(Hex.decode(test.hexDer), test.hexIeee.length() / 2));
    }
  }

  protected static final String[] invalidEcdsaDers =
      new String[] {
        "2006020101020101", // 1st byte is not 0x30 (SEQUENCE tag)
        "3006050101020101", // 3rd byte is not 0x02 (INTEGER tag)
        "3006020101050101", // 6th byte is not 0x02 (INTEGER tag)
        "308206020101020101", // long form length is not 0x81
        "30ff020101020101", // invalid total length
        "3006020201020101", // invalid rLength
        "3006020101020201", // invalid sLength
        "30060201ff020101", // no extra zero when highest bit of r is set
        "30060201010201ff", // no extra zero when highest bit of s is set
      };

  @Test
  public void testIsValidDerEncoding() throws Exception {
    for (String der : invalidEcdsaDers) {
      assertFalse(EllipticCurves.isValidDerEncoding(Hex.decode(der)));
    }
  }

  @Test
  public void testComputeSharedSecretWithWycheproofTestVectors() throws Exception {
    if (TestUtil.isTsan()) {
      return;
    }

    // NOTE(bleichen): Instead of ecdh_test.json it might be easier to use the
    //   files ecdh_<curve>_ecpoint.json, which encode the public key point just as DER
    //   encoded bitsequence.
    JsonObject json =
        WycheproofTestUtil.readJson("../wycheproof/testvectors/ecdh_test.json");
    int errors = 0;
    JsonArray testGroups = json.get("testGroups").getAsJsonArray();
    for (int i = 0; i < testGroups.size(); i++) {
      JsonObject group = testGroups.get(i).getAsJsonObject();
      JsonArray tests = group.get("tests").getAsJsonArray();
      String curve = group.get("curve").getAsString();
      if (!curve.equals("secp256r1") && !curve.equals("secp384r1") && !curve.equals("secp521r1")) {
        // Only NIST curves P-256, P-384 and P-521 are supported.
        continue;
      }
      EllipticCurves.CurveType curveType = WycheproofTestUtil.getCurveType(curve);
      for (int j = 0; j < tests.size(); j++) {
        JsonObject testcase = tests.get(j).getAsJsonObject();
        String tcId =
            String.format(
                "testcase %d (%s)",
                testcase.get("tcId").getAsInt(), testcase.get("comment").getAsString());
        String result = testcase.get("result").getAsString();
        String hexPubKey = testcase.get("public").getAsString();
        String expectedSharedSecret = testcase.get("shared").getAsString();
        String hexPrivKey = testcase.get("private").getAsString();
        if (hexPrivKey.length() % 2 == 1) {
          hexPrivKey = "0" + hexPrivKey;
        }
        KeyFactory kf = EngineFactory.KEY_FACTORY.getInstance("EC");
        try {
          ECPrivateKey privKey = EllipticCurves.getEcPrivateKey(curveType, Hex.decode(hexPrivKey));

          X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(Hex.decode(hexPubKey));
          ECPublicKey pubKey = (ECPublicKey) kf.generatePublic(x509keySpec);
          String sharedSecret = Hex.encode(EllipticCurves.computeSharedSecret(privKey, pubKey));
          if (result.equals("invalid")) {
            if (expectedSharedSecret.equals(sharedSecret)
                && WycheproofTestUtil.checkFlags(
                    testcase, "WrongOrder", "WeakPublicKey", "UnnamedCurve")) {
              System.out.println(
                  tcId + " accepted invalid parameters but shared secret is correct.");
            } else {
              System.out.println(
                  "FAIL " + tcId + " accepted invalid parameters, shared secret: " + sharedSecret);
              errors++;
            }
          } else if (!expectedSharedSecret.equals(sharedSecret)) {
            System.out.println(
                "FAIL "
                    + tcId
                    + " incorrect shared secret, computed: "
                    + sharedSecret
                    + " expected: "
                    + expectedSharedSecret);
            errors++;
          }
        } catch (GeneralSecurityException ex) {
          System.out.println(tcId + " threw exception: " + ex.toString());
          if (result.equals("valid")) {
            System.out.println("FAIL " + tcId + " exception: " + ex.toString());
            ex.printStackTrace();
            errors++;
          }
        } catch (Exception ex) {
          // Other exceptions typically indicate that something is wrong with the implementation.
          System.out.println("FAIL " + tcId + " exception: " + ex.toString());
          ex.printStackTrace();
          errors++;
        }
      }
    }
    assertEquals(0, errors);
  }

  @Test
  public void computeSharedSecretWithPublicPoint() throws Exception {
    // test vector from wycheproof's ecdh_secp256r1_ecpoint_test.json, normal case.
    ECPrivateKey ecPrivateKey =
        EllipticCurves.getEcPrivateKey(
            EllipticCurves.CurveType.NIST_P256,
            Hex.decode("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"));
    ECPoint ecPublicPoint =
        EllipticCurves.pointDecode(
            EllipticCurves.CurveType.NIST_P256,
            EllipticCurves.PointFormatType.UNCOMPRESSED,
            Hex.decode(
                "0462d5bd3372af75fe85a040715d0f502428e07046868b0bfdfa61d731afe44f26ac333a93"
                    + "a9e70a81cd5a95b5bf8d13990eb741c8c38872b4a07d275a014e30cf"));
    byte[] expected =
        Hex.decode("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285");

    byte[] sharedSecret = EllipticCurves.computeSharedSecret(ecPrivateKey, ecPublicPoint);
    assertThat(sharedSecret).isEqualTo(expected);

    ECPoint publicPointNotOnCurve =
        new ECPoint(ecPublicPoint.getAffineX(), ecPublicPoint.getAffineY().add(BigInteger.ONE));
    assertThrows(
        GeneralSecurityException.class,
        () -> EllipticCurves.computeSharedSecret(ecPrivateKey, publicPointNotOnCurve));
  }

  @Test
  public void computeSharedSecretWithPublicPointP256() throws Exception {
    // test vector from golang's crypto/ecdh/ecdh_test.go.
    ECPrivateKey ecPrivateKey =
        EllipticCurves.getEcPrivateKey(
            EllipticCurves.CurveType.NIST_P256,
            Hex.decode("7d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534"));
    ECPoint ecPublicPoint =
        EllipticCurves.pointDecode(
            EllipticCurves.CurveType.NIST_P256,
            EllipticCurves.PointFormatType.UNCOMPRESSED,
            Hex.decode(
                "04700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287"
                    + "db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac"));
    byte[] expected =
        Hex.decode("46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b");

    byte[] sharedSecret = EllipticCurves.computeSharedSecret(ecPrivateKey, ecPublicPoint);
    assertThat(sharedSecret).isEqualTo(expected);
  }

  @Test
  public void computeSharedSecretWithPublicPointP384() throws Exception {
    // test vector from golang's crypto/ecdh/ecdh_test.go.
    ECPrivateKey ecPrivateKey =
        EllipticCurves.getEcPrivateKey(
            EllipticCurves.CurveType.NIST_P384,
            Hex.decode(
                "3cc3122a68f0d95027ad38c067916ba0eb8c38894d22e1b15618b6818a661774ad463b205da88cf699ab4d43c9cf98a1"));
    ECPoint ecPublicPoint =
        EllipticCurves.pointDecode(
            EllipticCurves.CurveType.NIST_P384,
            EllipticCurves.PointFormatType.UNCOMPRESSED,
            Hex.decode(
                "04a7c76b970c3b5fe8b05d2838ae04ab47697b9eaf52e764592efda27fe7513272734466b400091adbf2d68c58e0c50066"
                    + "ac68f19f2e1cb879aed43a9969b91a0839c4c38a49749b661efedf243451915ed0905a32b060992b468c64766fc8437a"));
    byte[] expected =
        Hex.decode(
            "5f9d29dc5e31a163060356213669c8ce132e22f57c9a04f40ba7fcead493b457e5621e766c40a2e3d4d6a04b25e533f1");

    byte[] sharedSecret = EllipticCurves.computeSharedSecret(ecPrivateKey, ecPublicPoint);
    assertThat(sharedSecret).isEqualTo(expected);
  }

  @Test
  public void computeSharedSecretWithPublicPointP521() throws Exception {
    // test vector from golang's crypto/ecdh/ecdh_test.go.
    ECPrivateKey ecPrivateKey =
        EllipticCurves.getEcPrivateKey(
            EllipticCurves.CurveType.NIST_P521,
            Hex.decode(
                "017eecc07ab4b329068fba65e56a1f8890aa935e57134ae0ffcce802735151f4eac6564f6ee9974c5e6887a1fefee5743ae2241bfeb95d5ce31ddcb6f9edb4d6fc47"));
    ECPoint ecPublicPoint =
        EllipticCurves.pointDecode(
            EllipticCurves.CurveType.NIST_P521,
            EllipticCurves.PointFormatType.UNCOMPRESSED,
            Hex.decode(
                "0400685a48e86c79f0f0875f7bc18d25eb5fc8c0b07e5da4f4370f3a9490340854334b1e1b87fa395464c60626124a4e70d0f785601d37c09870ebf176666877a2046d"
                    + "01ba52c56fc8776d9e8f5db4f0cc27636d0b741bbe05400697942e80b739884a83bde99e0f6716939e632bc8986fa18dccd443a348b6c3e522497955a4f3c302f676"));
    byte[] expected =
        Hex.decode(
            "005fc70477c3e63bc3954bd0df3ea0d1f41ee21746ed95fc5e1fdf90930d5e136672d72cc770742d1711c3c3a4c334a0ad9759436a4d3c5bf6e74b9578fac148c831");

    byte[] sharedSecret = EllipticCurves.computeSharedSecret(ecPrivateKey, ecPublicPoint);
    assertThat(sharedSecret).isEqualTo(expected);
  }

  @Test
  public void validateSharedSecret() throws Exception {
    // test vector from wycheproof's ecdh_secp256r1_ecpoint_test.json, normal case.
    ECPrivateKey ecPrivateKey =
        EllipticCurves.getEcPrivateKey(
            EllipticCurves.CurveType.NIST_P256,
            Hex.decode("0612465c89a023ab17855b0a6bcebfd3febb53aef84138647b5352e02c10c346"));
    byte[] sharedSecret =
        Hex.decode("53020d908b0219328b658b525f26780e3ae12bcd952bb25a93bc0895e1714285");
    EllipticCurves.validateSharedSecret(sharedSecret, ecPrivateKey);

    // Most byte strings shorter than sharedSecret are valid.
    byte[] emptySharedSecret = new byte[0];
    EllipticCurves.validateSharedSecret(emptySharedSecret, ecPrivateKey);
    byte[] anotherSharedSecret = Hex.decode("00112233445566778899aabbccddeeff");
    EllipticCurves.validateSharedSecret(anotherSharedSecret, ecPrivateKey);
    byte[] ffSharedSecret = Hex.decode("ffffffffffffffffffffffffffffffffffffffff");
    EllipticCurves.validateSharedSecret(ffSharedSecret, ecPrivateKey);

    // The modulusMinus1 is not a valid secret, because computeY fails.
    byte[] modulusMinus1 =
        Hex.decode("00ffffffff00000001000000000000000000000000fffffffffffffffffffffffe");
    assertThrows(
        GeneralSecurityException.class,
        () -> EllipticCurves.validateSharedSecret(modulusMinus1, ecPrivateKey));

    // The modulus is not a valid secret, because it is out of range.
    byte[] modulus =
        Hex.decode("00ffffffff00000001000000000000000000000000ffffffffffffffffffffffff");
    assertThrows(
        GeneralSecurityException.class,
        () -> EllipticCurves.validateSharedSecret(modulus, ecPrivateKey));
  }
}
