package org.bouncycastle.pqc.jcajce.provider.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import junit.framework.TestCase;
import org.bouncycastle.pqc.jcajce.interfaces.SPHINCSPlusKey;
import org.bouncycastle.pqc.jcajce.interfaces.SPHINCSPlusPrivateKey;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.spec.SPHINCSPlusParameterSpec;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.test.FixedSecureRandom;

/**
 * Test cases for the use of SPHINCS+ with the BCPQC provider.
 */
public class SphincsPlusTest
    extends TestCase
{
    // test vector courtesy the "Yawning Angel" GO implementation and the SUPERCOP reference implementation.
    byte[] msg = Strings.toByteArray("Cthulhu Fthagn --What a wonderful phrase!Cthulhu Fthagn --Say it and you're crazed!");

    public void setUp()
    {
        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
        {
            Security.addProvider(new BouncyCastlePQCProvider());
        }
    }

//    public void testSphincsDefaultKeyGen()
//        throws Exception
//    {
//        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");
//
//        kpg.initialize(new SPHINCSPlusKeyGenParameterSpec(), new RiggedRandom());
//
//        KeyPair kp = kpg.generateKeyPair();
//
//        SPHINCSPlusKey pub = (SPHINCSPlusKey)kp.getPublic();
//
//        assertTrue(Arrays.areEqual(expSha2Pub, pub.getKeyData()));
//
//        SPHINCSPlusKey priv = (SPHINCSPlusKey)kp.getPrivate();
//
//        assertTrue(Arrays.areEqual(expSha2Priv, priv.getKeyData()));
//
//        KeyFactory keyFact = KeyFactory.getInstance("SPHINCSPlus", "BCPQC");
//
//        SPHINCSPlusKey pub2 = (SPHINCSPlusKey)keyFact.generatePublic(new X509EncodedKeySpec(pub.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha2Pub, pub2.getKeyData()));
//
//        SPHINCSPlusKey priv2 = (SPHINCSPlusKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(priv.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha2Priv, priv2.getKeyData()));
//    }

    public void testPrivateKeyRecovery()
        throws Exception
    {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.sha2_128f_robust, new RiggedRandom());

        KeyPair kp = kpg.generateKeyPair();

        KeyFactory kFact = KeyFactory.getInstance("SPHINCSPlus", "BCPQC");

        SPHINCSPlusKey privKey = (SPHINCSPlusKey)kFact.generatePrivate(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream oOut = new ObjectOutputStream(bOut);

        oOut.writeObject(privKey);

        oOut.close();

        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));

        SPHINCSPlusKey privKey2 = (SPHINCSPlusKey)oIn.readObject();

        assertEquals(privKey, privKey2);

        assertEquals(kp.getPublic(), ((SPHINCSPlusPrivateKey)privKey2).getPublicKey());
        assertEquals(((SPHINCSPlusPrivateKey)privKey).getPublicKey(), ((SPHINCSPlusPrivateKey)privKey2).getPublicKey());
    }

    public void testPublicKeyRecovery()
        throws Exception
    {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.sha2_128f_robust, new RiggedRandom());

        KeyPair kp = kpg.generateKeyPair();

        KeyFactory kFact = KeyFactory.getInstance("SPHINCSPlus", "BCPQC");

        SPHINCSPlusKey pubKey = (SPHINCSPlusKey)kFact.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded()));

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        ObjectOutputStream oOut = new ObjectOutputStream(bOut);

        oOut.writeObject(pubKey);

        oOut.close();

        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));

        SPHINCSPlusKey pubKey2 = (SPHINCSPlusKey)oIn.readObject();

        assertEquals(pubKey, pubKey2);
    }

//    public void testSphincsDefaultSha2KeyGen()
//        throws Exception
//    {
//        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");
//
//        kpg.initialize(new SPHINCSPlusKeyGenParameterSpec(SPHINCSPlusKeyGenParameterSpec.SHA512_256), new RiggedRandom());
//
//        KeyPair kp = kpg.generateKeyPair();
//
//        SPHINCSPlusKey pub = (SPHINCSPlusKey)kp.getPublic();
//
//        assertTrue(Arrays.areEqual(expSha2Pub, pub.getKeyData()));
//
//        SPHINCSPlusKey priv = (SPHINCSPlusKey)kp.getPrivate();
//
//        assertTrue(Arrays.areEqual(expSha2Priv, priv.getKeyData()));
//
//        KeyFactory keyFact = KeyFactory.getInstance("SPHINCSPlus", "BCPQC");
//
//        SPHINCSPlusKey pub2 = (SPHINCSPlusKey)keyFact.generatePublic(new X509EncodedKeySpec(pub.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha2Pub, pub2.getKeyData()));
//
//        SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(pub2.getEncoded());
//
//        assertEquals(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512_256), SPHINCSPlusKeyParams.getInstance(pkInfo.getAlgorithm().getParameters()).getTreeDigest());
//
//        SPHINCSPlusKey priv2 = (SPHINCSPlusKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(priv.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha2Priv, priv2.getKeyData()));
//    }
//
//    public void testSphincsDefaultSha3KeyGen()
//        throws Exception
//    {
//        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");
//
//        kpg.initialize(new SPHINCSPlusKeyGenParameterSpec(SPHINCSPlusKeyGenParameterSpec.SHA3_256), new RiggedRandom());
//
//        KeyPair kp = kpg.generateKeyPair();
//
//        SPHINCSPlusKey pub = (SPHINCSPlusKey)kp.getPublic();
//
//        assertTrue(Arrays.areEqual(expSha3Pub, pub.getKeyData()));
//
//        SPHINCSPlusKey priv = (SPHINCSPlusKey)kp.getPrivate();
//
//        assertTrue(Arrays.areEqual(expSha3Priv, priv.getKeyData()));
//
//        KeyFactory keyFact = KeyFactory.getInstance("SPHINCSPlus", "BCPQC");
//
//        SPHINCSPlusKey pub2 = (SPHINCSPlusKey)keyFact.generatePublic(new X509EncodedKeySpec(pub.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha3Pub, pub2.getKeyData()));
//
//        SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(pub2.getEncoded());
//
//        assertEquals(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha3_256), SPHINCSPlusKeyParams.getInstance(pkInfo.getAlgorithm().getParameters()).getTreeDigest());
//
//        SPHINCSPlusKey priv2 = (SPHINCSPlusKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(priv.getEncoded()));
//
//        assertTrue(Arrays.areEqual(expSha3Priv, priv2.getKeyData()));
//    }
//
//    public void testSphincsSha2Signature()
//        throws Exception
//    {
//        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");
//
//        kpg.initialize(new SPHINCSPlusKeyGenParameterSpec(SPHINCSPlusKeyGenParameterSpec.SHA512_256), new RiggedRandom());
//
//        KeyPair kp = kpg.generateKeyPair();
//
//        Signature sig = Signature.getInstance("SHA512withSPHINCSPlus", "BCPQC");
//
//        sig.initSign(kp.getPrivate());
//
//        sig.update(msg, 0, msg.length);
//
//        byte[] s = sig.sign();
//
//        assertTrue(Arrays.areEqual(expSha2Sig, s));
//    }
//
//    public void testSphincsSha3Signature()
//        throws Exception
//    {
//        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");
//
//        kpg.initialize(new SPHINCSPlusKeyGenParameterSpec(SPHINCSPlusKeyGenParameterSpec.SHA3_256), new RiggedRandom());
//
//        KeyPair kp = kpg.generateKeyPair();
//
//        Signature sig = Signature.getInstance("SHA3-512withSPHINCSPlus", "BCPQC");
//
//        sig.initSign(kp.getPrivate());
//
//        sig.update(msg, 0, msg.length);
//
//        byte[] s = sig.sign();
//
//        assertTrue(Arrays.areEqual(expSha3Sig, s));
//    }
//


    public void testSphincsRandomSigSHA2()
        throws Exception
    {
        SecureRandom random = new FixedSecureRandom(Hex.decode("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2DB505D7CFAD1B497499323C8686325E4711E95F8A383854BA16A5DD3E25FF71D3"
            + "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1"));
        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.sha2_128f_robust, random);

        KeyPair kp = kpg.generateKeyPair();

        Signature sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initSign(kp.getPrivate(), new FixedSecureRandom(Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1")));

        sig.update(msg, 0, msg.length);

        byte[] s = sig.sign();

        byte[] expected = Hex.decode("[encoded data, 34176 characters]");
        assertTrue(Hex.toHexString(s), Arrays.areEqual(expected, s));

        sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initVerify(kp.getPublic());

        sig.update(msg, 0, msg.length);

        assertTrue(sig.verify(s));
    }

    public void testSphincsDeterministicSigSHA2()
        throws Exception
    {
        SecureRandom random = new FixedSecureRandom(Hex.decode("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2DB505D7CFAD1B497499323C8686325E4711E95F8A383854BA16A5DD3E25FF71D3"
            + "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1"));
        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.sha2_128f_robust, random);

        KeyPair kp = kpg.generateKeyPair();

        Signature sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initSign(kp.getPrivate());

        sig.update(msg, 0, msg.length);

        byte[] s = sig.sign();

        byte[] expected = Hex.decode("[encoded data, 34176 characters]");
        assertTrue(Hex.toHexString(s), Arrays.areEqual(expected, s));

        sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initVerify(kp.getPublic());

        sig.update(msg, 0, msg.length);

        assertTrue(sig.verify(s));
    }

    public void testSphincsRandomSigHaraka()
        throws Exception
    {
        SecureRandom random = new FixedSecureRandom(Hex.decode("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2DB505D7CFAD1B497499323C8686325E4711E95F8A383854BA16A5DD3E25FF71D3"
            + "061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1"));
        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");

        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.haraka_128f, random);

        KeyPair kp = kpg.generateKeyPair();

        Signature sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initSign(kp.getPrivate(), new FixedSecureRandom(Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1")));

        sig.update(msg, 0, msg.length);

        byte[] s = sig.sign();

        byte[] expected = Hex.decode("[encoded data, 34176 characters]");
        assertTrue(Hex.toHexString(s), Arrays.areEqual(expected, s));

        sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initVerify(kp.getPublic());

        sig.update(msg, 0, msg.length);

        assertTrue(sig.verify(s));
    }

    public void testSphincsRandomSigSHAKE()
        throws Exception
    {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.shake_256f_robust, new SecureRandom());

        KeyPair kp = kpg.generateKeyPair();

        Signature sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initSign(kp.getPrivate(), new SecureRandom());

        sig.update(msg, 0, msg.length);

        byte[] s = sig.sign();

        sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initVerify(kp.getPublic());

        sig.update(msg, 0, msg.length);

        assertTrue(sig.verify(s));
    }

    public void testSphincsRandomSigSHAKE_Simple()
        throws Exception
    {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("SPHINCSPlus", "BCPQC");

        kpg.initialize(SPHINCSPlusParameterSpec.shake_256f, new SecureRandom());

        KeyPair kp = kpg.generateKeyPair();

        Signature sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initSign(kp.getPrivate(), new SecureRandom());

        sig.update(msg, 0, msg.length);

        byte[] s = sig.sign();

        sig = Signature.getInstance("SPHINCSPlus", "BCPQC");

        sig.initVerify(kp.getPublic());

        sig.update(msg, 0, msg.length);

        assertTrue(sig.verify(s));
    }

    private static class RiggedRandom
        extends SecureRandom
    {
        public void nextBytes(byte[] bytes)
        {
            for (int i = 0; i != bytes.length; i++)
            {
                bytes[i] = (byte)(i & 0xff);
            }
        }
    }
}

