// Copyright 2013, Square, Inc.
package com.squareup.crypto.rsa;

import java.math.BigInteger;
import java.security.SecureRandom;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.junit.Assert;

/** RSA test vectors. */
public final class NativeRSAVectors {

  public static class TestVector {
    public final BigInteger message;
    public final BigInteger signed;
    private final RSAPrivateCrtKeyParameters key;

    private static BigInteger decode(String hex) {
      return new BigInteger(hex, 16);
    }

    // CHECKSTYLE.OFF: ParameterNumber
    public TestVector(String message, String signed, String p, String dP, String q, String dQ,
        String qInv, String privateExponent, String modulus, String publicExponent) {
      this(decode(message), decode(signed),
          new RSAPrivateCrtKeyParameters(decode(modulus), decode(publicExponent),
              decode(privateExponent), decode(p), decode(q), decode(dP), decode(dQ), decode(qInv)));
    }
    // CHECKSTYLE.ON: ParameterNumber

    public TestVector(BigInteger message, BigInteger signed, RSAPrivateCrtKeyParameters key) {
      this.message = message;
      this.signed = signed;
      this.key = key;
    }

    public RSAPrivateCrtKeyParameters getPrivateKey() {
      return key;
    }

    public RSAKeyParameters getPublicKey() {
      return new RSAKeyParameters(false, key.getModulus(), key.getPublicExponent());
    }

    private static String hex(BigInteger value) {
      String result = value.toString(16);
      if (result.length() % 2 == 1) {
        result = "0" + result;
      }
      return result;
    }

    /** Outputs the buffer as Java hex string. */
    private static void printHexConstant(BigInteger value) {
      String hex = hex(value);
      System.out.print('"');
      boolean first = true;
      for (int i = 0; i < hex.length(); i += 80) {
        if (first) {
          first = false;
        } else {
          System.out.println('"');
          System.out.print("    + \"");
        }
        int end = Math.min(hex.length(), i + 80);
        System.out.print(hex.substring(i, end));
      }
      System.out.print('"');
    }

    public void printJavaConstructorFor() {
      System.out.println("new TestVector(");
      printHexConstant(message);
      System.out.println(',');
      printHexConstant(signed);
      System.out.println(',');
      printHexConstant(key.getP());
      System.out.println(',');
      printHexConstant(key.getDP());
      System.out.println(',');
      printHexConstant(key.getQ());
      System.out.println(',');
      printHexConstant(key.getDQ());
      System.out.println(',');
      printHexConstant(key.getQInv());
      System.out.println(',');
      printHexConstant(key.getExponent());
      System.out.println(',');
      printHexConstant(key.getModulus());
      System.out.println(',');
      printHexConstant(key.getPublicExponent());
      System.out.println(");");
    }
  }

  // Generated by main().
  public static final TestVector VECTOR1 = new TestVector(
      "6f519638774463912c6f45b35505a7498136d6ec5e3603bdebc70c839c9703f077eaf2e053bc6572"
          + "f01602d23ba7f4a24647b08a7de2ed4dcf5d1540493671a2",
      "2013391c0207181ef9050c75b8935efac252a109180b0fe57a5bbcd50ef643c061944a241949afed"
          + "56b48a5cf2fa94861445c2ba042605520296f1ae1220f047",
      "dce85acf2f4f8aff8c30a5259a9722a4d501316ea76c710f2f5454a3f8f663c9",
      "934591df74dfb1ffb2cb18c3bc64c1c338ab7649c4f2f60a1f8d8dc2a5f997db",
      "a3538816ee0da46b4929fa5744e170cd63a3e08c90764409ee0790571e783fab",
      "6ce25ab9f4091847861bfc3a2deba088ed17eb08604ed8069eafb58f69a57fc7",
      "49c3b079c92a8054318d19a572cb69db6c95c7141b4bb48f4a801b74e82cad4c",
      "5df559b9b94918e3d7266e710e98eb23a8e3887ba38298ec3b9668cbccec28443930a002cf1fef2d"
          + "d3c3200dff617b35472f91ac68b597a74b77078ac7f2fc8b",
      "8cf0069695eda555c2b9a5a995e560b57d554cb97543e56259619d31b3623c67d604d2ea540d162f"
          + "92ff4f91de8acc42236c6c7dd4f318940e8e704b435b1e43", "03");

  public static final TestVector VECTOR2 = new TestVector(
      "5b2db76130015d5d37e792d88e337238083419854a285b8edaf6a002f686c56447c029735e152e4f"
          + "dc3e0a20ac46fc7534a9ccf5cd4bc5d0c03d6a98889593bf1dc8b8f8f2c84fe76e5b4c1cedca8f15"
          + "fc00be453dd934acbb7ce25a2e12151f7877b0f0985ac861708e478aecb2e3100f1bb057f9aab6ad"
          + "eeb8b619262ccc04",
      "81fcb1e408fde7f24faa75e615ee8d05aad7dc1ffc50084b3c4c5bbbce3967d2e92dac5668ab8e96"
          + "617b819ea58d9507af9f3c2b99308c7ad50229352982311ae359a1e71b060278d33f333b938e15ba"
          + "f7449873952da5c2e5a1528f71a96d3ecdd73e3e4978467e778ae57084c5fe892042d7a7660f42f5"
          + "8bf5d2dcde36a6bf",
      "c6a81281a8373392411b68b3564974f831e143a54f3c6ecf7bfd73b468269f22208b28159595c6bf"
          + "c7ef556b0abdf58bfde991918483634e174246ad12500e79",
      "84700c567024cd0c2b679b2239864dfacbeb826e34d2f48a52a8f7cd9ac46a16c05cc563b90e847f"
          + "da9f8e475c7ea3b2a946610badacecdeba2c2f1e0c355efb",
      "ba504e74e31dd9fc9521a1cbf9bcf07730cc1961e330b015c6c75f65511b63dea8658b22cf5e12b1"
          + "3dcc8421949a976c93bcb6e6182131701be50b8b03ad7a97",
      "7c3589a34213e6a8636bc132a67df5a4cb32bb969775cab92f2f94ee361242947043b21734e961cb"
          + "7e8858166311ba48627dcf44101620f567ee07b20273a70f",
      "707555183d8a1f471eeae2c0deb4404e105195f169d7540e6a306cab32a9b757afa6fbf46435a4a5"
          + "6b8a7520fd0a0b83e66973aac303957855eecbb080d71f39",
      "6062f49144848238a3a0d34b13d3f99378f1940ed805298ea44c32d1575d3af2f6a709fd6f6cc852"
          + "570ac2ff6e131a8459255196d00abcbf75dcdc75461a067c4deeffcf6542d1351595f4250bfeafe1"
          + "d2dd40dd8fe09553b03ef97f7dfcbc60306bad32d588a4242d9f9ae2f48208dff34804d074ed3891"
          + "fd6a250ab5ee718b",
      "90946ed9e6c6c354f5713cf09dbdf65d356a5e164407be55f6724c3a030bd86c71fa8efc27232c7b"
          + "8290247f251ca7c685b7fa6238101b1f30cb4aafe92709bbf5dee0ada339475e769df8b6e2046d42"
          + "1ef93e538a3dfee2cb234958f63d1d9111923704a540cfa74a2b41e10e1b9a487e924fb04c086999"
          + "2f4689c826e3335f", "03");

  public static final TestVector VECTOR3 = new TestVector(
      "1796fc3df7b7d80c8168231a2c42c084ae6d1acb5e83fd4d18d210168b9ace4699e4f3dce06dfca8"
          + "f9f094b4a48390eb41c44bb0c2c9f305e33a68062b4121435af5d497fbbc83484ac01480463c4708"
          + "b7d567f1c9f61baba3d09195670ad2faac8e4cb6d2a131e70f55aab438eaf3e3935417bf431c788d"
          + "7f7d6f7c9d7229ef0b4a2ac0811aa7eb053091fd7b81999db88fb8205a019d197e3c94b6f57b5fc6"
          + "ddb159e4660b1b44f4616c8888138a942afb4aaaf53d06a08ed1a8b54c56c2517c90d8e09d256f96"
          + "486a092c4329764a50ee3f6859a06a7338eb06949b6e1afd78852f6b560ab4ad634895c9a6df91f8"
          + "62af34888031f16095980a8c4808bf3b",
      "2236fe645c6ab1e13067a6ea56f7c97450d36b44a3d60731d5bdc01a9b780488a628400268422349"
          + "6226058410306ed5cadeaf3600e0dde6779fd17959a73e4c3464ad4dc7ca85cff51b5a02f9f3e2a1"
          + "4b1365ba6e396d12406d0b7e6bd806e8f1b52aadf40ddc42d0f80bd63aab73b7d84818fd7a0ef566"
          + "44350ed241592ab6aa39cb908a82413f4cd629abeb1bcb74043e7a9c5573c5e853771cc214b5575c"
          + "ed4aa741d073fb3aa83d657a4a219c26b0e5d3070ca8ad083668a7c6e1df6de1c8e0ac8cbac9cafc"
          + "e24140beac1fe6aded1f71b98a0df6726127bccb13f840efff14d6b855d5d5bf54766e3a441dfddc"
          + "7830133ce35f825c17372d51976f89ca",
      "e8d1f3c2fdb5c55a41594493df497d2c52fbe8ec7dc1cf2ebaa28f3a3e82ecfe632588ac6cc22a30"
          + "1c73f760808b7279977367264a9f734622dacda1b0437e5bbf6ebee514bfc4fb2521a796080b5314"
          + "ffb12cf84cf666e4e2dcbcfefcea61f11a7c4e4816c5fdf42b7d9d05a037db8709a12c66c6f24d5f"
          + "17ff72161fe102f5",
      "9b36a281fe792e3c2b90d86294dba8c837529b4853d68a1f27170a26d45748a9976e5b1d9dd6c6ca"
          + "bda2a4eb005cf6fbba4cef6edc6a4cd96c91de6bcad7a9927f9f29ee0dd52dfcc36bc50eb0078cb8"
          + "aa761dfaddf999edec9328a9fdf196a0bc52dedab9d953f81cfe68ae6acfe7af5bc0c84484a188ea"
          + "0fffa1641540aca3",
      "a5d11df9631ade978f1006ea2218e41044dbdeb143935912c002cfedfd7e894ab2a566afa90046ce"
          + "7e9c6d0ed565d2571fb59975563eb1821afd0480db4366d80971015c04d10211bcbf63fe16677f45"
          + "4e850e447eb31a6f70c9df4cc8920b5990c3bbf77e0634cddd10a1afbca33265a67480676f4499d3"
          + "b107d5d128c7991f",
      "6e8b6950ecbc94650a0aaf46c165ed602de7e9cb82623b61d557354953a9b0dc7718ef1fc6002f34"
          + "546848b48e43e18f6a7910f8e429cbac11fe0300922cef3ab0f600e803360161287f97feb99a54d8"
          + "df035ed8547766f4f5dbea3330615ce660827d4fa95978893e0b1675286ccc43c44daaef9f831137"
          + "cb5a8e8b708510bf",
      "1f5f1c9cc56ee1ff7c8ba85c5115011eba4ac135a1a0c098a96f403915d5bee7630d424c6e1c8704"
          + "c8bed135c62418a21858e60c4aef0505219568f5b3c4e22c30aad0bb94c9ad497aaa125537f599e0"
          + "29248e08af67e62c1a4da3e682a7b0b6b7ac60922c04ac83d3fe904d0daa72b8e50dc40b41c8696a"
          + "7d8b2a7298946b14",
      "64890084d9c81159d0cf3d1a230c29b1e260737bc91d356eabf67c0c102704e38497b14f5d6ab88d"
          + "f469bda1db7d2b98d8bdd9b32d81ebde23d583423abdb45a060f3bb02291930165885a3ded14c40e"
          + "97bd79ee259a00d859ddd39f9b957131ac7d9da4f88d6e5fea3317a1303aae7470ca8fdce5f99d6c"
          + "3df39e76230ed91e1b4880b1c2e3606f3207f663c0972b070ccb95e4acab6b00a3539819a83cb17e"
          + "d9bee7f6e5a6cd011e6d07fe0e66a044d7c1c2b6d2c625e164df7a43b58e82de966f1682c8e9ec1e"
          + "a198cdd843ebdd4f0208f9517620ff0edae4caba9acadde67c4eb3f89783c3cf1d3884cc7ca32396"
          + "c4861d9357a3d12e6ec0a37b17341dbb",
      "96cd80c746ac1a06b936dba734923e8ad390ad39adabd02601f1ba12183a875546e389f70c2014d4"
          + "ee9e9c72c93bc165451cc68cc442e1cd35c044e3581c8e870916d98833da5c82184c875ce39f2615"
          + "e39c36e53867014486ccbd6f696029ca82bc6c7774d4258fdf4ca371c85805aea92fd7cb58f66c22"
          + "5ced6db1349645aeb78fd2c70525b4989b753d13a24521c72b092874c45648c26fa2c34eb85c8087"
          + "5c694b4e6e3ca48048b3f06c6b8b3537facba4addd075d9a552709881bdca981aa86620546efa93a"
          + "d446405884549e50d143b136fcdaffea9bfdcc63adacba2465b618347811d878b46305e817cfc34e"
          + "d6ded92b39aca0f86f283d1feb76c8ab", "03");

  private static final SecureRandom SECURE_RANDOM = new SecureRandom();
  private static final BigInteger SIGNING_EXPONENT = BigInteger.valueOf(3);

  public static void main(String[] args) throws Exception {
    // Generate new test vectors in Java format.
    System.out.println("---- NEW CONSTANTS ----");
    generateTestVector(512, 1);
    generateTestVector(1024, 2);
    generateTestVector(2048, 3);
  }

  private static void generateTestVector(int rsaKeyBits, int suffix) throws Exception {
    AsymmetricCipherKeyPair pair = generateKeyPair(rsaKeyBits);
    RSAPrivateCrtKeyParameters priv = (RSAPrivateCrtKeyParameters) pair.getPrivate();

    byte[] message = new byte[rsaKeyBits / 8];
    SECURE_RANDOM.nextBytes(message);
    // Clear the top bit to ensure it fits.
    message[0] &= 0x7F;

    RSAEngine encoder = new RSAEngine();
    encoder.init(true, pair.getPrivate());
    byte[] signed = encoder.processBlock(message, 0, message.length);

    RSAEngine decoder = new RSAEngine();
    decoder.init(false, pair.getPublic());
    byte[] decoded = decoder.processBlock(signed, 0, message.length);

    Assert.assertArrayEquals(message, decoded);

    System.out.println("public static final TestVector VECTOR" + suffix + " = ");
    new TestVector(new BigInteger(1, message), new BigInteger(1, signed),
        (RSAPrivateCrtKeyParameters) pair.getPrivate()).printJavaConstructorFor();
    System.out.println();
  }

  private static AsymmetricCipherKeyPair generateKeyPair(int rsaKeyBits) throws Exception {
    RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
    generator.init(new RSAKeyGenerationParameters(SIGNING_EXPONENT, SECURE_RANDOM, rsaKeyBits, 12));
    return generator.generateKeyPair();
  }

  private NativeRSAVectors() {
  }
}