package org.bouncycastle.pqc.crypto.test;

import java.io.IOException;
import java.security.SecureRandom;

import junit.framework.TestCase;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
import org.bouncycastle.pqc.crypto.lms.LMSKeyGenerationParameters;
import org.bouncycastle.pqc.crypto.lms.LMSKeyPairGenerator;
import org.bouncycastle.pqc.crypto.lms.LMSParameters;
import org.bouncycastle.pqc.crypto.lms.LMSPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
import org.bouncycastle.pqc.crypto.lms.LMSSigner;
import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
import org.bouncycastle.pqc.crypto.util.PrivateKeyInfoFactory;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;

public class LMSTest
    extends TestCase
{
    public void testKeyGenAndSign()
        throws Exception
    {
        byte[] msg = Strings.toByteArray("Hello, world!");
        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();

        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4), new SecureRandom()));

        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();

        LMSSigner signer = new LMSSigner();

        signer.init(true, kp.getPrivate());

        byte[] sig = signer.generateSignature(msg);

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg, sig));
    }

    public void testKeyGenAndSignSha256_192()
        throws Exception
    {
        byte[] msg = Strings.toByteArray("Hello, world!");
        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();

        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_sha256_n24_h5, LMOtsParameters.sha256_n24_w4), new SecureRandom()));

        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();

        LMSSigner signer = new LMSSigner();

        signer.init(true, kp.getPrivate());

        byte[] sig = signer.generateSignature(msg);

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg, sig));
    }
    
    public void testKeyGenAndSignShake256_192()
        throws Exception
    {
        byte[] msg = Strings.toByteArray("Hello, world!");
        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();

        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_shake256_n24_h5, LMOtsParameters.shake256_n24_w4), new SecureRandom()));

        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();

        LMSSigner signer = new LMSSigner();

        signer.init(true, kp.getPrivate());

        byte[] sig = signer.generateSignature(msg);

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg, sig));
    }

    public void testKeyGenAndSignTwoSigsWithShard()
        throws Exception
    {
        byte[] msg1 = Strings.toByteArray("Hello, world!");
        byte[] msg2 = Strings.toByteArray("Now is the time");

        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();

        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4), new SecureRandom()));

        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();

        LMSPrivateKeyParameters privKey = ((LMSPrivateKeyParameters)kp.getPrivate()).extractKeyShard(2);

        assertEquals(2, ((LMSPrivateKeyParameters)kp.getPrivate()).getIndex());

        LMSSigner signer = new LMSSigner();

        assertEquals(2, privKey.getUsagesRemaining());
        assertEquals(2, privKey.getIndexLimit());
        assertEquals(0, privKey.getIndex());

        signer.init(true, privKey);

        byte[] sig1 = signer.generateSignature(msg1);

        assertEquals(1, privKey.getIndex());

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg1, sig1));

        signer.init(true, privKey);

        byte[] sig = signer.generateSignature(msg2);

        assertEquals(2, privKey.getIndex());

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg2, sig));

        try
        {
            sig = signer.generateSignature(msg2);
            fail("no exception");
        }
        catch (ExhaustedPrivateKeyException e)
        {
            assertEquals("ots private key exhausted", e.getMessage());
        }

        signer.init(true, ((LMSPrivateKeyParameters)kp.getPrivate()));

        sig = signer.generateSignature(msg1);

        assertEquals(3, ((LMSPrivateKeyParameters)kp.getPrivate()).getIndex());

        assertFalse(Arrays.areEqual(sig1, sig));

        signer.init(false, kp.getPublic());

        assertTrue(signer.verifySignature(msg1, sig1));

        PrivateKeyInfo pInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(kp.getPrivate());
        AsymmetricKeyParameter pKey = PrivateKeyFactory.createKey(pInfo.getEncoded());

        signer.init(false, ((HSSPrivateKeyParameters)pKey).getPublicKey());

        assertTrue(signer.verifySignature(msg1, sig1));
    }

    public void test_shouldRecogniseInvalidSignature()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "0000001800000010D88330CA119BBFF43228CD435B63966089A997C6083CC2AF9BFE4A9789888694EC2A92883B36015A");
        byte[] msg = Hex.decode("900E8860388B34E30A103184F8EA198CC8B397AD5D7D99C554B966B3D18E938754B77F8252642A17A6F75CEA0945D4385BC37B75ED67C20F90DAC462A3DADB67FB3BC9CC23754F43298515208C2AA0181C098DACF58EC21D84A2F7A2A8539CBB17E915AA5EF042E9707104B08546B1887D6CB54096F25010E527BD655EBFB52D");
        byte[] sig = Hex.decode("00AFBA640000001030734BD0D2A048BAFE3FDDE5828D5D1431A6B02B9BC4F428266277D400CB1DD82E8939940D4E4835825088D6B5ED893B285E1136E39F06BD41DDA933847B6B0E2CDDDEFC7B2041BB75D10CD9904D6665A03C9BFA82989BEB903F0E8B8ECB2BC8C356CCB55225B6D0ED03EDD57B28ACC7911397FAE097F7B8BB888464FA56DC5D18483C5406A9FB51B361ACD45025304210A7031D18CC692F62797F0BB1682DC424DC309616D26C2FFF12DB9B7AC9BED9E35FC31D6113422D14EED59078AA29CE5E0C02F63D3CCBC870F1D5BA65C16DD42D2A373D51ED384307F9C412F7D6B4B7ED7A1C3EDD749BCCFDAEE66F1922249F9DF3EEB3452481B20DF8C63576680F27FE727AD7207389F9335844F8AEE3B8F31DB99D317174F04A133D4E011F352C74E1B3A5D2CA921D03A395B917FD486BC8C59E06C1BB1F49BF1556DCE070C3E94FD14877AC84A86A83C868F5E2307141972A7F7C6823C1A7ED9DEE07B10E0C9C6D65D004B3E841ADD1C3DE4CBBEBA9701B40DAD2D6016A88B68EF76F974B6942B4F13388A47131BC41F4BADAAABA37965EBE1566A921A7BDA4960F77638B5145AE6A8BCCBB96C0D9A433DB2AE60F85CD43963A12BD555786F887794C8734A3C329E85138958DCB5AF95C0E430220514525F65CCDB2DF360546CDE80525739DEEC5947F25A8F9F5FE7CB775A73112367D7C171EBDC9164568AF4F40399B7FA170A7F145A98E03955A3226E2946510E763B56AA619B46325E98B5E7CB5AC07E1ED42A010ECC807BDF4C6809908B8E7C03734B8A979A89A63FEC1F9C6F3878A73954E8C4A1C1914A2356D82A4F4804EEDE288A75BF755654F26AC4955428E9FE81289E0A55B2AF7BA235C98ACD89485EE04DCDC337228FF1B0C465C5FA7456FB1DCEB6D2DFF6C95431E6600000015EDED6089F9AA257DE3C3FD6E9AFB511E2A344A5DB043D5C6CE97D8648C4B125EAC8DD85036291DAF73F32CFAD7FEB07297DDFBECF0471EDC2908C3BE91A4DCB8CF532C9F37E5A4E704A1196405C71115FC622BD51F993D44D3116B0CA31D538E4249B54A67D13C8A02B5434C761B04CE43224D252079231EA031164C8BC3D51F5634FE881C6A0376FA5F0A9FFC5BD6F0118847CC793BCB57CC9C4D4602B0234CD03B8D3C259AF1B3A70C312070D3E8762BBECA5B3E951B3F29C51B6C1A75D7606B89653E9E0148C3FE095752FDC65D3AA2C854240ABE63440204C29BBF4C81D6D87D2FB522CCA245E03132DBA4DC49E193387C7E127C40A7F9B4995752DB1EB347DD5E1FE00203C78AC297E87F56C0F8C581ECBA774CC55BBD03EF335390F7E04CDA220220D1B4870379598583C057327053F7179D3802501E01162D5DB837B344E428443DCAE9B00FE01DF67627BAEBCD60D44A9B21EC0B6576E50DAB72CD0D55A7023391B393311B3B256A3BC42045208E118847F12633FB1890D78F0A8CD5614075A02046215D850112D111414906394FB2096E414AA90591857486945BF57D30706D2491C00796599E7DC1CB7504BAC7A7C7BA5CF0195AAE8162422DCE061C907DBD21A26BE844D6251AECA33DEC28F57C2A0C38D3D4E2894B5B7F9393AD52849D531E7EA95C6D19483FDA55DD9A9E3A4028BB6C7B90A7FD1B65B4EBCC12AFEEA0382E24CDD3261DCDC1E7A9A4227FE76C11EA73EDE79F8E332727767484123500EC3D82256843503DD771B0BE626D7BBDD2AE838A166032F8ACC635B4B9FEED9A31EFFCC87320D969FC0FE3890DBE40268922D12ABE");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        LMSSigner lmsSigner = new LMSSigner();

        lmsSigner.init(false, pup);

        assertFalse(lmsSigner.verifySignature(msg, sig));
    }

    public void test_should_verify_sha256_n24_w1()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "0000000A00000005BB9C1B5FCF1286C41F7DCDE9FB53A539C7E3E5EEF5FAB878BD36C04EAB54DEF7CC258C32A842C4FD");
        byte[] msg = Hex.decode("1BCC459B62CDA60458092ABF9BA110DEDC38B6CD819FDA8AEB7ABF935919F60FD73D6F4108CD86243D317802EE46392E7FC50BF3D7A80D8A0D4E68E8F1E336D00C1905D4CF90A4E82B8D758E29E8D85462B17790E90328DD123ED1ACB5B2B2B3C0A2EAC79A9BDFF655F5F600851FC8DEFA1E9AA397F672F6E0A06FAE86F9DD22");
        byte[] sig = Hex.decode("[encoded data, 9912 characters]");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n24_w1()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "000000180000000DE188C16F2640A361227C63532B1003E806038B1ADF081AFD9D4C2C5702BAE427DCA7FD40D8135830");
        byte[] msg = Hex.decode("37965CE6378A0EAC5FF4180AFE4D08A23047F9781540263FBC5488F2760D2247032827FB45D6688370BBF25FA1A9D401350EE3D63714045E91A2BDA00E7E685153BAB4D34E3DB1141E318C199FB7E27AF49771DF5F7501B7FB7639F7EB86B5FDD591178324C898A6ED434B39CCFFD5CB99A926C37C3EE36A3DD89BE7E49F5769");
        byte[] sig = Hex.decode("[encoded data, 10872 characters]");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n24_w2()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "000000170000000E5AB18087CB8CBE2C7884853AC5FB8E6E59EAA74E7DCD418FC0B4545BF12FC894B163C438C7B993D2");
        byte[] msg = Hex.decode("E95E2F0D429AC1A228DFE1F4D11675562A9DE1AA22AEC7E769F8609E2467083BC9382017FBAD3AFF52B1B91F7DEC31B6099F6F3F5A85060A762BBA7C991353E1FF5FE003CC7FF699DC3C3028B83E122475FAB27849089BAA3BED97F147163B82D9725E8F10A7B0BC17B947F52F41F70D1D0193D5952D535DEFF0AB7D3B0E5224");
        byte[] sig = Hex.decode("[encoded data, 5880 characters]");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n24_w4()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "000000150000000F054479157C09EB7E34A7916BF4C034E24F02017B1465BE851833CB98DB4D7F8BDAADAE44FF2071BB");
        byte[] msg = Hex.decode("34A6368F588463E30473D6AEA5F2A00DAF793569ED56264209B3FDEAFDD9A8A4DCB0B82EAC503F2A939ABDCE273F3D246DF3E62791E4DCECD4E3E098AFF67DC141FDEEE3160E0096A1C74DCA43FA25E94BADE07A9BEAA2CA93CF3CADE7E0F1E16500C84D2989D14E6E821234AF70BC1090C779A1D8E2C20A4A176CE85C6E18B3");
        byte[] sig = Hex.decode("000001140000000F610AF02360FC98234DEA82F7ABFBE4D7172D1472B5AA50EE293C8137FD43508400233BD5F3CE2656C12FDFC2235D7BF081AB8D6444DD41B9FA393362C0A7DD8737880AECB9503C95AD5A02FC1E10BCEC808CA381972A282245BEC9FD146947E0E6E55F417A2851D48A1EA235D2268E84E87BB254477528C70F16AF8F9060D483777A586080E072B6333463D033F3636DCF738A8EBC25D39A0922A53346E9C16A66F4F3086BAA8EEFDE0236BAE1C2AD34175FF7902A0623BC7E70C7399D28134CA7845BCF2503B729ED7DA7DDEE5C67183883BB3BD819777CBB6CECEA92412C1660310ED4860F242F380B86202764C0658293BC3A8582F18F3D1AD17328EE3EDF91E2483A6F4C4B2F80B19C949DAFC1EABBF989185E5F84D9B0C20F00BB4299CB600A5E7A113A0B7C51C413ADCC91237625FE2B101C563AFD92CFAA2CB46635871FD1F53EECB75F1B34C016F1C5B3681A3E78BB99006AA207B694FEA2EF248AFC76A8DD493BF75A5F2B04D791014429DA4B45AD29943FD1A63F4DB14DA8C66651EA066177B425D01F8A7085E1CCC7A8F03690292C1217A94EB10329222BFFE29DECA42C57353CB2EF5870F2280E30C4FA336D5EFEBDDE938A81EC79A35A55814E4FCDCDC281FDB38319ADCB579F3983050556AC8B18CE1F23F7563D7393CF5A19D993955AE9D481D24855CEB03B7083C087DEBE441CB3D779702BDB6F123DAC4D3520CF787FACC9C1FB9EA9F4A16173822FF65DF93F657B3D16113CBB6A9AF37E4FB6FD3161FB8F20BA6B57FBE787AE8F8DE22B1A14FD5BA3DCADD8F3B73479086EE09814503D60C95709886B066C6A93DBA5C42F507CDD4DCA930A1806000D8F3F5B13C70F807EB4CA6C47FAB4014FE69D7CD6EFC1176E075B4B5F849EA48EF124E0AFBEB97FE6A00B0ECBD91D118ACF34F963E716362B6DBED5175BBEDF5BB8ACCBA80C2B1D3B2906D49CF27380406062947D5869669A524FD1AFBA970541921D6A7E7B849905699ED414CFEED47414BE208895E352AF7A8D6ECC3C0903F5F9748B0B2A6A7E333789F063DD522E7473C518CD0E4AF6F7286332A9E4EC3A33E01B3809E7B3C64525F8FB65E22FDE422FD9BF3D491CA2FCC43A8558C1AD0DE7092AE4CF5F87F5B7E871C32C2F0E70E07BCBDC3062F50630F1EDEDE66337404D3A5968139276A6034A2326F27BB22310A109A3E3DD40E20F34FD2E6716304D946441BE5227867D316D687955B975D4E19476C70103865AB935E8CB347EDCE783E9DDE38C84E277E9FCAF0B0F6B37F6E2CE17D36EB2DEBD43521A5AAA2475FF851C02617B4F9EE62687497BD62CFDC553A89DFAF553C70D20B4EBA42600D343649ECD45D1F6EF2E2F42592A9D7B90ABBCFD64A1E742F1C3C000D13ED0ED5B66AF879342CC24A44E2B84E400D0298E9923306F6D4832DF847E37F79E58457B3BA5FF4E46B41419E8F7D884311EDDA47B6575EF3571513FA24D9FAC2596707234D67127C60145F18E669B2F1BF06BD27572F56B539EA61A6CA98DB4347F3E89D482C17A963B6E8148870F18B8C9DA51216925C86A0C9FBDE75F13663C236A041463B40FD187FDCA53E6823EA55A4BBE9062AA45535F5EABAD2BC23777882E0F0C71E4E0881598D86B0F588DB9C90550B587D5B33A8D9B33980B03247E722843EB54C94E5E18A22B3326FBA8FE917C3048EBADD2CC415B49EC754504A689B96C86E6AB15A7A315A6041EA71960992DEE40E36A23D28AC8682D3C7E977D3EE295D8E92900000015675208ED60F2C8D6FC359396B44719BE9615452BCB68F35A7FBB1FF9335A421AADE038D188A1941D16FCF525958E678BCE6D17682CBAD986AB6AE78CD1ED56D1530DAB56EF8AE0FCEE2CB7882F55D1233046A38E21F7EB8C4F8E9C60F2DC692E562C113E2A5058AF5F319CFB5C0F9F344CB3219313A70CEE7C2D82AF77B0418AC61411A87A20B01EB39931457CB2946C551EF17E0021669A3658F71B37E1D7B7647EC6E08444E9B7AEE8789D79276CE53AE47734002F1E1E2D426C95E454E1B3F4FE2B2E3F12EB1CE541D4021E294F3C31C79A2A2D8E8652A371D3302D8EC4C987557836C9809210D167CFE47EDA0AAD");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n24_w8()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "00000016000000104A90A81A143279659797B572866CAD245B3E55268DDF686AAE7BB9A5E77304A0C8220ECB42BD0088");
        byte[] msg = Hex.decode("0B2A3604599F2675E41098DF4EF0A570270FD86C3531E33F6C7AF3820F8BE2C1D0F4138657244ECA0AB02CF32CCEA7BD0EEC561EC9DFAA1FB08D29A86930261103C4AEF5ADB35D7D2DA24CDF5093CC95225DB3369CE6867EEAC139B4A27C6C56907D86F964CA1BC123F2044F5E94E0EA42CB24D687B68E8ABD13C0C974807032");
        byte[] sig = Hex.decode("00002F9C0000001063557BCA0E9BEF9B863138BF1701A277520C6E7E020AC0ECC0877DA741B5F21B93A98EBA55DDA341B24CE7AA336204AE0144BA4716E3304B66BEA54FB3D4177078727A5420BDA5F3E2D0F9C643CDA3B38FCF8DD3E02D169C0A8B3797BD013B6C28F26A8C2993F7712B0DA09929FF80A2CA21A6E4949558ECE114826DF6F3D8C620A1323C2CC8DCF79F917A2FF891B8B375465A77D67ED9486B4FA4F27F022E66CE076F8C908FB2BDD822B1F890E68AE57DD28CBC8943F67B74D1A3D074CB41F32658C563AF59AF583B488DBB34F52622E2359AB0805E1F4E2769C2AC4DDC3595018EE4C27799BBF3C30987263F5CAF5566E7581CCE73B1652D6BE4F8E1F6A55E00FC4F263132A025507F342B914152F9A5DC05E0EB980637C19A250B8CDBC7B68266695FC803DE027121C635438C1408CEBCD9FAD46586EEF23FEA11A724B5F515600BAF951EC8617DDB3E5FAF5D13314266F4C0F3429327E46A580213134954C4971E53B45B322CC7FF003D1C7716DBB5CE34BA8778DB8E09F399A7A99FAF402EE97F6C007FB1647DB87B0D5AE05165BA192D32AA33E3246C85D02ED388333F02BB96217C2DCC45F01791907A78FF52DF69337F3AB4D0F2515752CF75960BBBBC09020651738495AE0C5A159040E07293FDB98B8665E82166D15F5960D3A58988E9466DE59ACF4D724D254BE72820FE9A8E634B351D4B8C07E911D23FC8B0E87684BFE54C50444665FBC0FA6430D61A8BB24E2AC08BB8A7BB39866C61D5180D6F0D1DB0411A923F5050FE3E2CF2CD0692A9593EBB2D0C71221AFF04AA489218A0A5451273B2D1EB136B1758B77CC113186A2319313C06489ABE3CE22B932D2010716A06F1DC76F0BF519C16248D21544447E1FB7A002EFB39F8CC38968E1907E8DEBE076656762A0000001648AB38A35FF650BBEE245BE96EB43AC44A4C8B03D1F33CA59025C728C5703088A157BF9CDFBDDDA49E46F8A2606217C8C3AD12E68BAF369E40E24C05297383583A88DDE17F34D7347C46C6CB400239A0DE9DA2197A178C6551D563FB6A12BBE4F6524CB80B3DCFA75052DD0969C453132F7BB1636CE1B221434309D860ADBAFA86620A9778E03DE506E0C56F1D4E024B66643F2E4AE2065619575483F31ECA4DD7FA2D81180DBB9DCFE60CE660051751A4958F1683EFC5622DCD25613463F27CBD31E67FA1A84DCAEBE12787509EF6F9FEBFA83CE35A2DF445E59F60A097BEE4BE0D58068F6DA39800045458DA799ED07EE05309DF9D62D1869662A975101D1C4004B301CBE12B98DEECF2B07F3C9D0FDDE64181D4C568DD34655EE9C514576F422992E80AC9F4BE79A068F44042487B454948B58F474037C06F6973142109D1028127765236E4EB93DB6CF02D09281C8A0C690339B25BE4ED52A0D47367BCB5A77957A932FDA1A0");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);
        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n24_w2_2()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "000000140000000EF45E0A263A79A7304D94B38738FA7E8AF4D7503DCC65163326FBF7AC0C8C938B2D1324456FB956D7");
        byte[] msg = Hex.decode("2EB454D4116CECF6A6CF72BEFDDCB5B6204E5689BF8901B5926692BA04BC7EBC8EC21E2B4645C99F85C9E4D9A3A855594D019264DC1C034DE09B1ADA0B71C08BA81567533503B8B2B232A5C3D6E150AC409D2CC54727D2F616532FFEF87CD12B8CBBBBF45596F779C6D98BAF121AA010991256DC9C4D46640ABCEA2C7D7E8C3E");
        byte[] sig = Hex.decode("[encoded data, 5160 characters]");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);

        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n32_w8()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "000000130000000C972FE777D40FE1EFD97603A62733CD1CD2D6F13C54A085F23017BBD46965C0853ABC363FABCED9119A26337A306A7B88");
        byte[] msg = Hex.decode("901F4B2AD9C744E3818519BD93C0578AC9AA6FE2B3BC3FE35EA569847C1FE797D240D846C2BFAD1FD72A784F46F393B1C1035170AEA0A569650DE986E71B0EF9D560B02772198D1B1A1E825926A5F1482C509F8A18BDF782674A5F4FDBC83D59839EF4373BBF01618D6A2DA5220DB4C55CC54F25759297C997ECE7737A1B8648");
        byte[] sig = Hex.decode("01BEA7690000000C85018B7FBF611F91DBFACC1B753B62F70652C4D50E2304209B56B2796B1F640845C5DC51829CB63C945637C786C63C5C6FF2246FA8C4F3ACABAF9CFC988486C99ABE9603E434D84EDE0A792A910870C32E0AA3C8A75E354F253F15A2D614DF441082663A96B7BDE3A3F1DFE06A98AC4AB4F104929A7E2E4EE8092B9209B0D9D0EF8A3CE9108692737B4116176C53264D4C69FBFD6669033B596065FE540BF0CF02E7F814EF821FAA5D06CF3C5C258A95A74939CEC696AB12ACBE7D2C1828249EB1AB2866B95BA1BB54BADA4E0BDC4C2F85B922B6E78793BE654F12F6C47DCB2A6505601BEE6EAD8A54CA4EAE6F778433C19FF61A6D9277DE3AE206F8C56D86839360AB4E07C1FA39D04303306967E41480C817CF1A0A9E1E731EB725138A7C6DD2D19D03AEF13032846AD0B55BCD0949B2CF48B8144F0DB17848BB957DDFB1FACD6234435D48012720F78BA6AC44531D5F98694D7C590FC69BCD332246DA9D522B423E5D2956C8A742F9E00327806B4D443EE4DF953BFDF5213FC791240326AB1D842FABCEA8896669B602AAFCCE63CC9D1AA12DE6E5E40F268F2B3AA53E10D892E3440AB8FC55D5ED743CA6E08B8615832AD3E8E30A68445DAF88F7262A6D308069627293376A30911BBB3674D099DE2DA8B700E66112EE98FA00A3C883B1FF614A685B108FE310EBF5D4FCDB457717E0287D6A3DBD71CD2B3D8B24865831291D7A4139FC36549A32EB9495A11CE6C39363528922923C5F11FCE3DC62AE05F8BA557896B28E7A280B84C7F5FBB0F6C9EB101EF95016662B8BA1856C40BA0FB074BB9136D497D90C280DFC3D4D3DF702EE22C1DED8656FE1068CB312DBA589F8529B192C3AE46BD63CCA9DBA00A9001198298E33064A187EDDBB3844469424868BC36CC54B67A427AE716616FC5EA856B6FCC08D3DCD293EC12927CC4C8E6B5A3043A03CA7DFAAADEEABDA6E5CF3D70B3F82E0132A84561FE182AAC77D180D6F787BD99BA1E1C4C4FD8CE7513A4E2446D48994BD7D58C79923B282BE71E4591812AB6D1C55A09BBF995909FAB9932668EFB3C30F123D3AA463B473037A36770325A25F401B32E6A1195D559B73EE53EA4C59BB402C21EC3CBD91B225CD059A1A9D32BBC720FD7549F0E2369F4093121A0B079D3BE5E4248EA97677FF16AFDC6193F898ADD87CD603965FDFF5887DBE29DC8E4F7CC6A7D90C9CB090AFDFCDE260CBFBE773FAAD9B59A5E8338FB918498BB333DA1877A7D1343B5903353F7AC8B79980FB062BE0D8F4DE892C647A47B1FCC058C3FA1539928854A86B7B48CA66A61B8AB79F3F4D8925D9E7472A10D3500509904A9D014ACE9B987AF34E4A893F7A87E1E52E90A0A20CD5D9D07522A4E1E6FA5D2A5A7E333D8084068A26E3758C132C111DFBA3061AA3CDBB4D0D563A3073815A1FF99FFCE701CA90747D8060C91937A3E2A0AE7781385179723EE95302C0C77C5BA2433C5B13D90B27100BAEC47C3C22C4942FF4C94543D86FB50CD6B3C551944AACB833AA76F27F6A120CB7531D6D04568CB50CB92F5EC35E8B9080E171367FD49F07B728AB210A2D1B845B92E7000000136B31C160A7BEFC143EE9A4DB1234C3BF42E11D0BE58BE77A9DBA08E9863C4737A363A15DF0D3D699365A6BAFEFDFA12DF37EC7FFE1AD91F0F0E2849F2E2207E804E244A81211B1FF8242DA525F45AAC685C0CEA936984D848376834065250025AEA126EA4C1138FD4C7AFC2C7698108A767A953EF7CDE4998FFF65B4FD4D21251F2E91411D121B6E025B6BDF6A4819AF9E094C511CD558CF65CB818A2B8EAD0936BEFDF0775C1F0A3BB67C81170C0B35F0818C747E01BF97C88FD70A93AEDE476B07BD76D4211A6650FA7485B0A985010C14A9A05776DCA4F9E8442AD1D99C57A4760342B925A0510D4132B504AB7AEB9A8FC3F43BD3CBD0EC08AAB1543C2DEAEA41FBEBAA3B18E59D5300822C2AD4ACB651FD16ADC158B9D07B3A63A6045AC0C3A9BDF37B3DF5A6186DC7EA543FFE995B6FC1C0BDEC121326A10AB866B15E9E59F8EEC40E13AEFA8B455C6093C2D0FB919BE4E3CC70A484A26DE71F11264D3E1BFD153C744EEAFFF8AFE6B08C9FF11363CCFDDA59A690A9CAB0743505A1AA99397AE4410EEA5718F003C846332F1976067224574C716A07B896668902E9701D431BF91B3E3A8CDE1E1E6680538E2BB6845D2405E7319D1731D48A40BA818BD42B1D00BA0AC6914D4E80DE31766C32C03DA53DBD20F8E773F84A79829919CA1B7FCB7CDDAD1F1710B23D19897F30E1752BF9A805A657D0CF762FE3174D450C8090A4442CAE229919C80E39550E32FF9741FD02CFF3C012E77722125D3252E144F5F6AE985324254A1582FF06F5EE565815CD0C9B820593B481C8CE3BB1CA01E51E35E8664FAF0A13ABA483B8E478DE4DD04F3CFB8A180CDC04BD2367037038CB027E146485EBE2E425AD9DC60AF5DFC1746E60265546C0B1B0513B5D047C3320A69619548D1F5143592034260B0A1777DA81C080637F4FE2DDFB94CDBFD1C09344FF72A661474721B0EEC6B52E812A8F81613EC4088F28B95F3B0D94F419AE0627121F05337872642EECE6529AE7DB49AB9A38A62AC80DD2241FA61F1A80536D75F519CA0F0145E77B9C682C47EA9D88B5CA609C1E915C37B667099FC61A15E7109DFBF24BF067271B2025C6473AB268E385CF103B0149F41F2141F312F531E4");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);

        validateSig(pup, sig, msg);
    }

    public void test_should_verify_shake_n32_w1()
        throws Exception
    {
        byte[] lmsPub = Hex.decode(
            "0000001100000009747BBE12A6157879FF4FD22FEBCDB557661035CE843BBFD05D1D83E97F9635762D6E274278F02129E3E6E2E012859F1E");
        byte[] msg = Hex.decode("DDABE5B8C14869E4B912CFABE87998D626B47E608D6250946CD6D5815DB5F38DC4D7F77BFF6C12D02C86CC9A9B6F33EAF76E025F90BA3CD527E425D318966A86AC6A1A1C648F4428130F1AC937777992AAEE17C73BA0F8DEF45C0B449525DF474F04121501DDD80F353EFF899830313229BAF9F8DDBA8ABF801CFA3C9E7A6713");
        byte[] sig = Hex.decode("[encoded data, 18008 characters]");

        LMSPublicKeyParameters pup = LMSPublicKeyParameters.getInstance(lmsPub);

        validateSig(pup, sig, msg);
    }

    private void validateSig(LMSPublicKeyParameters lmsPub, byte[] sig, byte[] msg)
    {
        LMSSigner lmsSigner = new LMSSigner();

        lmsSigner.init(false, lmsPub);

        assertTrue(lmsSigner.verifySignature(msg, sig));
    }

    /**
     * RFC 8554 / NIST SP 800-208: an attacker-supplied LMS signature carries the LM-OTS type code
     * in its bytes. An out-of-table type code must be rejected with a clean parse failure, not an
     * unchecked NullPointerException (a remote denial-of-service when verifying a malformed
     * signature). See COVERAGE_BUGS_HANDOVER.md finding #17.
     */
    public void test_malformedSignatureUnknownLmOtsType()
        throws Exception
    {
        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();
        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4), new SecureRandom()));
        LMSPublicKeyParameters pubKey = (LMSPublicKeyParameters)kpGen.generateKeyPair().getPublic();

        // q (4 bytes) followed by an unknown LM-OTS type code (0x7FFFFFFF).
        byte[] malformed = Hex.decode("000000007fffffff");

        try
        {
            pubKey.generateLMSContext(malformed);
            fail("malformed signature accepted");
        }
        catch (NullPointerException e)
        {
            fail("unknown LM-OTS type code threw NullPointerException instead of a clean parse failure");
        }
        catch (IllegalStateException e)
        {
            // expected: generateLMSContext wraps the parse IOException as an IllegalStateException.
            assertTrue(e.getCause() instanceof IOException);
        }
    }

    /**
     * The companion of {@link #test_malformedSignatureUnknownLmOtsType()} for the LMS (tree) type
     * code, which the parser reads after the LM-OTS signature. The LM-OTS portion below is
     * well-formed (valid type, zero-filled C and y); only the trailing LMS type code is unknown.
     */
    public void test_malformedSignatureUnknownLmsType()
        throws Exception
    {
        AsymmetricCipherKeyPairGenerator kpGen = new LMSKeyPairGenerator();
        kpGen.init(new LMSKeyGenerationParameters(
            new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4), new SecureRandom()));
        LMSPublicKeyParameters pubKey = (LMSPublicKeyParameters)kpGen.generateKeyPair().getPublic();

        LMOtsParameters ots = LMOtsParameters.sha256_n32_w4;
        int otsSigLen = 4 + ots.getN() + ots.getP() * ots.getN();    // LM-OTS type || C || y
        byte[] malformed = new byte[4 + otsSigLen + 4];              // q || LM-OTS signature || LMS type
        Pack.intToBigEndian(ots.getType(), malformed, 4);            // valid LM-OTS type
        Pack.intToBigEndian(0x7FFFFFFF, malformed, 4 + otsSigLen);   // unknown LMS type

        try
        {
            pubKey.generateLMSContext(malformed);
            fail("malformed signature accepted");
        }
        catch (NullPointerException e)
        {
            fail("unknown LMS type code threw NullPointerException instead of a clean parse failure");
        }
        catch (IllegalStateException e)
        {
            assertTrue(e.getCause() instanceof IOException);
        }
    }
}
