bouncycastle: Android tree with upstream code for version 1.68

Bug: 179780002
Test: this change is only for diffing against master
Change-Id: Ia554de2d89ad9416580ebba8872b85fc0e5a51cd
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/CertUtils.java b/bcpkix/src/main/java/org/bouncycastle/cert/CertUtils.java
index c43ef83..c71be36 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/CertUtils.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/CertUtils.java
@@ -33,6 +33,7 @@
 import org.bouncycastle.asn1.x509.TBSCertList;
 import org.bouncycastle.asn1.x509.TBSCertificate;
 import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.util.Properties;
 
 class CertUtils
 {
@@ -229,31 +230,44 @@
     static boolean isAlgIdEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2)
     {
         if (!id1.getAlgorithm().equals(id2.getAlgorithm()))
-        {
-            return false;
-        }
+         {
+             return false;
+         }
 
-        if (id1.getParameters() == null)
-        {
-            if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
-            {
-                return false;
-            }
+         if (Properties.isOverrideSet("org.bouncycastle.x509.allow_absent_equiv_NULL"))
+         {
+             if (id1.getParameters() == null)
+             {
+                 if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
+                 {
+                     return false;
+                 }
 
-            return true;
-        }
+                 return true;
+             }
 
-        if (id2.getParameters() == null)
-        {
-            if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
-            {
-                return false;
-            }
+             if (id2.getParameters() == null)
+             {
+                 if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
+                 {
+                     return false;
+                 }
 
-            return true;
-        }
+                 return true;
+             }
+         }
 
-        return id1.getParameters().equals(id2.getParameters());
+         if (id1.getParameters() != null)
+         {
+             return id1.getParameters().equals(id2.getParameters());
+         }
+
+         if (id2.getParameters() != null)
+         {
+             return id2.getParameters().equals(id1.getParameters());
+         }
+
+         return true;
     }
 
     static ExtensionsGenerator doReplaceExtension(ExtensionsGenerator extGenerator, Extension ext)
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/X509v1CertificateBuilder.java b/bcpkix/src/main/java/org/bouncycastle/cert/X509v1CertificateBuilder.java
index 3652ba9..dba72fc 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/X509v1CertificateBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/X509v1CertificateBuilder.java
@@ -6,11 +6,9 @@
 
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.asn1.x509.ExtensionsGenerator;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.Time;
 import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
 import org.bouncycastle.operator.ContentSigner;
 
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/cmp/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/cmp/package.html
index a58af18..b01e408 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/cmp/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/cmp/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 Basic support package for handling and creating CMP (RFC 4210) certificate management messages.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/cmp/test/AllTests.java b/bcpkix/src/main/java/org/bouncycastle/cert/cmp/test/AllTests.java
index 05d5946..d7bf2b8 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/cmp/test/AllTests.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/cmp/test/AllTests.java
@@ -30,6 +30,7 @@
 import org.bouncycastle.asn1.cmp.PKIStatusInfo;
 import org.bouncycastle.asn1.crmf.CertReqMessages;
 import org.bouncycastle.asn1.crmf.CertReqMsg;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 import org.bouncycastle.asn1.crmf.ProofOfPossession;
 import org.bouncycastle.asn1.crmf.SubsequentMessage;
@@ -297,8 +298,8 @@
         CertifiedKeyPair certKp = response.getCertifiedKeyPair();
 
         // steps to unwrap private key
-        EncryptedValue encValue = certKp.getPrivateKey();
-
+        EncryptedKey encKey = certKp.getPrivateKey();
+        EncryptedValue encValue = EncryptedValue.getInstance(encKey.getValue());
         // recover symmetric key
         AsymmetricKeyUnwrapper unwrapper = new JceAsymmetricKeyUnwrapper(encValue.getKeyAlg(), kp.getPrivate());
         
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/bc/BcCRMFEncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/bc/BcCRMFEncryptorBuilder.java
index df40133..92ab8ac 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/bc/BcCRMFEncryptorBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/bc/BcCRMFEncryptorBuilder.java
@@ -58,10 +58,7 @@
         CRMFOutputEncryptor(ASN1ObjectIdentifier encryptionOID, int keySize, SecureRandom random)
             throws CRMFException
         {
-            if (random == null)
-            {
-                random = CryptoServicesRegistrar.getSecureRandom();
-            }
+            random = CryptoServicesRegistrar.getSecureRandom(random);
 
             CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, random);
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/jcajce/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/jcajce/package.html
index e9bc53f..b8b14ee 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/jcajce/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/jcajce/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 JCA extensions to the CRMF online certificate request package.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/package.html
index 521fc44..e9992c4 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/crmf/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/crmf/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 Basic support package for handling and creating CRMF (RFC 4211) certificate request messages.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java b/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
index 2b64340..742caae 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/JcaX500NameUtil.java
@@ -2,28 +2,80 @@
 
 import java.security.cert.X509Certificate;
 
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.X500NameStyle;
+import org.bouncycastle.jcajce.interfaces.BCX509Certificate;
 
 public class JcaX500NameUtil
 {
     public static X500Name getIssuer(X509Certificate certificate)
     {
-        return X500Name.getInstance(certificate.getIssuerX500Principal().getEncoded());
-    }
-
-    public static X500Name getSubject(X509Certificate certificate)
-    {
-        return X500Name.getInstance(certificate.getSubjectX500Principal().getEncoded());
+        if (certificate instanceof BCX509Certificate)
+        {
+            return notNull(((BCX509Certificate)certificate).getIssuerX500Name());
+        }
+        return getX500Name(certificate.getIssuerX500Principal());
     }
 
     public static X500Name getIssuer(X500NameStyle style, X509Certificate certificate)
     {
-        return X500Name.getInstance(style, certificate.getIssuerX500Principal().getEncoded());
+        if (certificate instanceof BCX509Certificate)
+        {
+            return X500Name.getInstance(style, notNull(((BCX509Certificate)certificate).getIssuerX500Name()));
+        }
+        return getX500Name(style, certificate.getIssuerX500Principal());
+    }
+
+    public static X500Name getSubject(X509Certificate certificate)
+    {
+        if (certificate instanceof BCX509Certificate)
+        {
+            return notNull(((BCX509Certificate)certificate).getSubjectX500Name());
+        }
+        return getX500Name(certificate.getSubjectX500Principal());
     }
 
     public static X500Name getSubject(X500NameStyle style, X509Certificate certificate)
     {
-        return X500Name.getInstance(style, certificate.getSubjectX500Principal().getEncoded());
+        if (certificate instanceof BCX509Certificate)
+        {
+            return X500Name.getInstance(style, notNull(((BCX509Certificate)certificate).getSubjectX500Name()));
+        }
+        return getX500Name(style, certificate.getSubjectX500Principal());
+    }
+
+    public static X500Name getX500Name(X500Principal principal)
+    {
+        return X500Name.getInstance(getEncoded(principal));
+    }
+
+    public static X500Name getX500Name(X500NameStyle style, X500Principal principal)
+    {
+        return X500Name.getInstance(style, getEncoded(principal));
+    }
+
+    private static X500Name notNull(X500Name name)
+    {
+        if (null == name)
+        {
+            throw new IllegalStateException();
+        }
+        return name;
+    }
+
+    private static X500Principal notNull(X500Principal principal)
+    {
+        if (null == principal)
+        {
+            throw new IllegalStateException();
+        }
+        return principal;
+    }
+
+    private static byte[] getEncoded(X500Principal principal)
+    {
+        return notNull(principal).getEncoded();
     }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/package.html
index cc15e01..96667c3 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/jcajce/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 JCA extensions to the certificate building and processing package.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPResp.java b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPResp.java
index ed3918a..6e7d893 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPResp.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/OCSPResp.java
@@ -74,7 +74,7 @@
 
     public int getStatus()
     {
-        return this.resp.getResponseStatus().getValue().intValue();
+        return this.resp.getResponseStatus().getIntValue();
     }
 
     public Object getResponseObject()
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java
index 94bf52f..78ed990 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/JcaBasicOCSPRespBuilder.java
@@ -2,6 +2,8 @@
 
 import java.security.PublicKey;
 
+import javax.security.auth.x500.X500Principal;
+
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
 import org.bouncycastle.cert.ocsp.OCSPException;
@@ -10,6 +12,11 @@
 public class JcaBasicOCSPRespBuilder
     extends BasicOCSPRespBuilder
 {
+    public JcaBasicOCSPRespBuilder(X500Principal principal)
+    {
+        super(new JcaRespID(principal));
+    }
+
     public JcaBasicOCSPRespBuilder(PublicKey key, DigestCalculator digCalc)
         throws OCSPException
     {
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/package.html
index cfe87f2..b9e5cae 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/jcajce/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 JCA extensions to the OCSP online certificate status package.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/package.html
index 234cb32..4d2bd5e 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 Basic support package for handling and creating OCSP (RFC 2560) online certificate status requests.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/OCSPTestUtil.java b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/OCSPTestUtil.java
index f6dd98c..b01d24a 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/OCSPTestUtil.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/OCSPTestUtil.java
@@ -12,18 +12,34 @@
 
 import javax.crypto.KeyGenerator;
 
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AccessDescription;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
 import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x509.X509Name;
+import org.bouncycastle.cert.X509v1CertificateBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
 import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v1CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 import org.bouncycastle.x509.X509V3CertificateGenerator;
 
 public class OCSPTestUtil
 {
-    
+    private static final String BC = "BC";
+
     public static SecureRandom     rand;
     public static KeyPairGenerator kpg, eckpg;
     public static KeyGenerator     desede128kg;
@@ -65,12 +81,104 @@
         return eckpg.generateKeyPair();
     }
 
-    public static X509Certificate makeCertificate(KeyPair _subKP,
-            String _subDN, KeyPair _issKP, String _issDN)
-            throws Exception
+    public static X509Certificate makeCertificate(KeyPair _subKP, String _subDN)
+        throws Exception
     {
+        return makeCertificate(_subKP, _subDN, _subKP, _subDN, false);
+    }
 
-        return makeCertificate(_subKP, _subDN, _issKP, _issDN, false);
+    public static X509Certificate makeRootCertificate(KeyPair _subKP, String _subDN)
+        throws Exception
+    {
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(_subKP.getPrivate());
+        X509v1CertificateBuilder certGen = new JcaX509v1CertificateBuilder(
+            new X500Name(_subDN), allocateSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),
+            new X500Name(_subDN), _subKP.getPublic());
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+    }
+
+    public static X509Certificate makeCertificate(KeyPair _subKP, String _subDN, KeyPair _issKP, X509Certificate _issCert, boolean _ca)
+        throws Exception
+    {
+        org.bouncycastle.asn1.x509.Certificate cert =  org.bouncycastle.asn1.x509.Certificate.getInstance(_issCert.getEncoded());
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(_issKP.getPrivate());
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            cert.getSubject(), allocateSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),
+            new X500Name(_subDN), _subKP.getPublic());
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        certGen.addExtension(
+            Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(_issCert));
+
+        certGen.addExtension(
+            Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(_subKP.getPublic()));
+
+        certGen.addExtension(
+            Extension.basicConstraints, false, new BasicConstraints(_ca));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+    }
+
+    public static X509Certificate makeCertificateWithOCSP(KeyPair _subKP, String _subDN, KeyPair _issKP, X509Certificate _issCert, boolean _ca, String uri)
+        throws Exception
+    {
+        org.bouncycastle.asn1.x509.Certificate cert =  org.bouncycastle.asn1.x509.Certificate.getInstance(_issCert.getEncoded());
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(_issKP.getPrivate());
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            cert.getSubject(), allocateSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),
+            new X500Name(_subDN), _subKP.getPublic());
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        certGen.addExtension(
+            Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(_issCert));
+
+        certGen.addExtension(
+            Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(_subKP.getPublic()));
+
+        certGen.addExtension(
+            Extension.basicConstraints, false, new BasicConstraints(_ca));
+
+        certGen.addExtension(
+            Extension.authorityInfoAccess, false, new AuthorityInformationAccess(new AccessDescription(AccessDescription.id_ad_ocsp,
+                                                        new GeneralName(GeneralName.uniformResourceIdentifier, uri))));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+    }
+
+    public static X509Certificate makeCertificate(KeyPair _subKP, String _subDN, KeyPair _issKP, X509Certificate _issCert, KeyPurposeId keyPurpose)
+        throws Exception
+    {
+        org.bouncycastle.asn1.x509.Certificate cert =  org.bouncycastle.asn1.x509.Certificate.getInstance(_issCert.getEncoded());
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(_issKP.getPrivate());
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            cert.getSubject(), allocateSerialNumber(),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000),
+            new X500Name(_subDN), _subKP.getPublic());
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        certGen.addExtension(
+            Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(_issCert));
+
+        certGen.addExtension(
+            Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(_subKP.getPublic()));
+
+        certGen.addExtension(
+            Extension.basicConstraints, false, new BasicConstraints(false));
+
+        certGen.addExtension(
+            Extension.extendedKeyUsage, false, new ExtendedKeyUsage(keyPurpose));
+
+        return new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
     }
 
     public static X509Certificate makeECDSACertificate(KeyPair _subKP,
@@ -93,7 +201,7 @@
             String _subDN, KeyPair _issKP, String _issDN, boolean _ca)
             throws Exception
     {
-        return makeCertificate(_subKP,_subDN, _issKP, _issDN, "MD5withRSA", _ca);
+        return makeCertificate(_subKP,_subDN, _issKP, _issDN, "SHA1withRSA", _ca);
     }
 
     public static X509Certificate makeECDSACertificate(KeyPair _subKP,
@@ -104,6 +212,13 @@
     }
 
     public static X509Certificate makeCertificate(KeyPair _subKP,
+            String _subDN, KeyPair _issKP, String _issDN)
+            throws Exception
+    {
+        return makeCertificate(_subKP, _subDN, _issKP, _issDN, "SHA1withRSA", false);
+    }
+
+    public static X509Certificate makeCertificate(KeyPair _subKP,
             String _subDN, KeyPair _issKP, String _issDN, String algorithm, boolean _ca)
             throws Exception
     {
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/PKIXRevocationTest.java b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/PKIXRevocationTest.java
new file mode 100644
index 0000000..888548f
--- /dev/null
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/ocsp/test/PKIXRevocationTest.java
@@ -0,0 +1,646 @@
+package org.bouncycastle.cert.ocsp.test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.bouncycastle.cert.ocsp.RevokedStatus;
+import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.operator.DigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class PKIXRevocationTest
+    extends SimpleTest
+{
+    private static final String BC = "BC";
+    private static final int TEST_OCSP_RESPONDER_PORT = 10541;
+
+    public String getName()
+    {
+        return "PKIXRevocationTest";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", BC);
+
+        DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider(BC).build();
+
+        KeyPair rootKp = OCSPTestUtil.makeKeyPair();
+        KeyPair caKp = OCSPTestUtil.makeKeyPair();
+        KeyPair eeKp = OCSPTestUtil.makeKeyPair();
+        KeyPair ocspKp = OCSPTestUtil.makeKeyPair();
+
+        X509Certificate root = OCSPTestUtil.makeRootCertificate(rootKp, "CN=Root");
+        X509Certificate ca = OCSPTestUtil.makeCertificate(caKp, "CN=CA", rootKp, root, true);
+        X509Certificate ee = OCSPTestUtil.makeCertificate(eeKp, "CN=EE", caKp, ca, false);
+        X509Certificate ocsp = OCSPTestUtil.makeRootCertificate(ocspKp, "CN=OCSP");
+
+        byte[] eeResp = getOcspResponse(ocspKp, digCalcProv, ca, ee);
+        byte[] caResp = getOcspResponse(ocspKp, digCalcProv, root, ca);
+
+        List list = new ArrayList();
+        list.add(ee);
+        list.add(ca);
+
+        CertPath certPath = cf.generateCertPath(list);
+
+        Set trust = new HashSet();
+        trust.add(new TrustAnchor(root, null));
+
+        // EE Only
+        CertPathValidator cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        PKIXRevocationChecker rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        Map responses = new HashMap();
+
+        responses.put(ee, eeResp);
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.ONLY_END_ENTITY));
+
+        PKIXParameters param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+
+        // CA and EE
+
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        try
+        {
+            cpv.validate(certPath, param);
+            fail("no exception ca check");
+        }
+        catch (CertPathValidatorException e)
+        {
+            // ignore -- should fail as can't tell status of CA.
+        }
+
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        responses = new HashMap();
+
+        responses.put(ee, eeResp);
+        responses.put(ca, caResp);
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+
+        // EE revoke
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        responses = new HashMap();
+
+        responses.put(ee, getRevokedOcspResponse(ocspKp, digCalcProv, ca, ee));
+        responses.put(ca, caResp);
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        try
+        {
+            cpv.validate(certPath, param);
+            fail("no exception");
+        }
+        catch (CertPathValidatorException e)
+        {
+            isEquals(0, e.getIndex());
+            isTrue(e.getMessage().startsWith("certificate revoked, reason=(CRLReason: keyCompromise)"));
+        }
+
+        // EE request not successful
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        responses = new HashMap();
+
+        responses.put(ee, getFailedOcspResponse(ocspKp, digCalcProv, ca, ee));
+        responses.put(ca, caResp);
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        try
+        {
+            cpv.validate(certPath, param);
+            fail("no exception");
+        }
+        catch (CertPathValidatorException e)
+        {
+            isEquals(0, e.getIndex());
+            isTrue(e.getMessage().startsWith("OCSP response failed: "));
+        }
+
+        // EE only, OCSP responder
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOcspResponder(new URI("http://localhost:" + TEST_OCSP_RESPONDER_PORT + "/"));
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.ONLY_END_ENTITY));
+        rv.setOcspResponderCert(ocsp);
+
+        final byte[] nonce = new DEROctetString(Hex.decode("DEADBEEF")).getEncoded();
+
+        List<java.security.cert.Extension> extensions = new ArrayList<java.security.cert.Extension>();
+
+        extensions.add(new NonceExtension(nonce));
+
+        rv.setOcspExtensions(extensions);
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        Thread ocspResponder = new Thread(new OCSPResponderTask(TEST_OCSP_RESPONDER_PORT, getOcspResponse(ocspKp, digCalcProv, ca, ee, nonce)));
+
+        ocspResponder.setDaemon(true);
+        ocspResponder.start();
+
+        cpv.validate(certPath, param);
+
+        // faulty OCSP responder certificate
+        ocsp = OCSPTestUtil.makeCertificate(ocspKp, "CN=OCSP", caKp, ca, KeyPurposeId.id_kp_codeSigning);
+
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+        // need to avoid cache.
+        rv.setOcspResponder(new URI("http://localhost:" + (TEST_OCSP_RESPONDER_PORT + 1) + "/"));
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.ONLY_END_ENTITY));
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        ocspResponder = new Thread(new OCSPResponderTask(
+            TEST_OCSP_RESPONDER_PORT + 1,
+            getOcspResponse(ocspKp, ocsp, digCalcProv, ca, ee)));
+
+        ocspResponder.setDaemon(true);
+        ocspResponder.start();
+
+        try
+        {
+            cpv.validate(certPath, param);
+            fail("no exception");
+        }
+        catch (CertPathValidatorException e)
+        {
+            isEquals(0, e.getIndex());
+            isTrue(e.getMessage().equals("responder certificate not valid for signing OCSP responses"));
+        }
+
+        // corrected certificate
+        ocsp = OCSPTestUtil.makeCertificate(ocspKp, "CN=OCSP", caKp, ca, KeyPurposeId.id_kp_OCSPSigning);
+
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOcspResponder(new URI("http://localhost:" + (TEST_OCSP_RESPONDER_PORT + 2) + "/"));
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.ONLY_END_ENTITY));
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        ocspResponder = new Thread(new OCSPResponderTask(
+            TEST_OCSP_RESPONDER_PORT + 2,
+            getOcspResponse(ocspKp, ocsp, digCalcProv, ca, ee)));
+
+        ocspResponder.setDaemon(true);
+        ocspResponder.start();
+
+        cpv.validate(certPath, param);
+
+        // EE Only, CA using responder URL
+        ca = OCSPTestUtil.makeCertificateWithOCSP(caKp, "CN=CA", rootKp, root, true, "http://localhost:" + TEST_OCSP_RESPONDER_PORT + "/");
+        ee = OCSPTestUtil.makeCertificate(eeKp, "CN=EE", caKp, ca, false);
+
+        eeResp = getOcspResponseName(caKp, digCalcProv, ca, ee);
+        caResp = getOcspResponse(ocspKp, digCalcProv, root, ca);
+
+        list = new ArrayList();
+        list.add(ee);
+        list.add(ca);
+
+        certPath = cf.generateCertPath(list);
+        cpv = CertPathValidator.getInstance("PKIX", "BC");
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        responses = new HashMap();
+
+        responses.put(ee, eeResp);
+
+        rv.setOcspResponses(responses);
+
+        rv.setOcspResponderCert(ocsp);
+
+        ocspResponder = new Thread(new OCSPResponderTask(TEST_OCSP_RESPONDER_PORT, caResp));
+
+        ocspResponder.setDaemon(true);
+        ocspResponder.start();
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+
+        ocspCertChainTest();
+        dispPointCertChainTest();
+    }
+
+    private void ocspCertChainTest()
+        throws Exception
+    {
+        PEMParser parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ee.pem")));
+
+        X509CertificateHolder c1 = (X509CertificateHolder)parser.readObject();
+
+        parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ca.pem")));
+
+        X509CertificateHolder c2 = (X509CertificateHolder)parser.readObject();
+
+        parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ta.pem")));
+
+        X509CertificateHolder c3 = (X509CertificateHolder)parser.readObject();
+
+        JcaX509CertificateConverter conv = new JcaX509CertificateConverter().setProvider("BC");
+
+        List t = new ArrayList();
+
+        t.add(conv.getCertificate(c1));
+        t.add(conv.getCertificate(c2));
+
+        System.setProperty("org.bouncycastle.x509.enableCRLDP", "true");
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
+
+        CertPath certPath = cf.generateCertPath(t);
+
+        Set trust = new HashSet();
+        trust.add(new TrustAnchor(conv.getCertificate(c3), null));
+
+        // EE Only
+        CertPathValidator cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        PKIXRevocationChecker rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.NO_FALLBACK));
+
+        PKIXParameters param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+    }
+
+    private void dispPointCertChainTest()
+        throws Exception
+    {
+        PEMParser parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ee.pem")));
+
+        X509CertificateHolder c1 = (X509CertificateHolder)parser.readObject();
+
+        parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ca.pem")));
+
+        X509CertificateHolder c2 = (X509CertificateHolder)parser.readObject();
+
+        parser = new PEMParser(new InputStreamReader(this.getClass().getResourceAsStream("ta.pem")));
+
+        X509CertificateHolder c3 = (X509CertificateHolder)parser.readObject();
+
+        JcaX509CertificateConverter conv = new JcaX509CertificateConverter().setProvider("BC");
+
+        List t = new ArrayList();
+
+        t.add(conv.getCertificate(c1));
+        t.add(conv.getCertificate(c2));
+
+        System.setProperty("org.bouncycastle.x509.enableCRLDP", "true");
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
+
+        CertPath certPath = cf.generateCertPath(t);
+
+        Set trust = new HashSet();
+        trust.add(new TrustAnchor(conv.getCertificate(c3), null));
+
+        // EE and CA
+        CertPathValidator cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        PKIXRevocationChecker rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.PREFER_CRLS));
+
+        PKIXParameters param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+
+        // exercise cache
+        certPath = cf.generateCertPath(t);
+
+        trust = new HashSet();
+        trust.add(new TrustAnchor(conv.getCertificate(c3), null));
+
+        // EE and CA
+        cpv = CertPathValidator.getInstance("PKIX", BC);
+
+        rv = (PKIXRevocationChecker)cpv.getRevocationChecker();
+
+        rv.setOptions(Collections.singleton(PKIXRevocationChecker.Option.PREFER_CRLS));
+
+        param = new PKIXParameters(trust);
+
+        param.addCertPathChecker(rv);
+
+        cpv.validate(certPath, param);
+        System.setProperty("org.bouncycastle.x509.enableCRLDP", "");
+    }
+
+    private byte[] getOcspResponse(KeyPair ocspKp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(ocspKp.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, CertificateStatus.GOOD);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), null, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private byte[] getOcspResponseName(KeyPair ocspKp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(issuerCert.getSubjectX500Principal());
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, CertificateStatus.GOOD);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), null, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private byte[] getOcspResponse(KeyPair ocspKp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert, byte[] nonce)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(ocspKp.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, CertificateStatus.GOOD);
+
+        Extensions exts = new Extensions(new Extension[]
+            { new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, nonce) });
+
+        respGen.setResponseExtensions(exts);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), null, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private byte[] getOcspResponse(KeyPair ocspKp, X509Certificate ocsp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(ocspKp.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, CertificateStatus.GOOD);
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), new X509CertificateHolder[] { new JcaX509CertificateHolder(ocsp) }, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private byte[] getRevokedOcspResponse(KeyPair ocspKp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(ocspKp.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, new RevokedStatus(new Date(), CRLReason.keyCompromise));
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), null, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.SUCCESSFUL, resp).getEncoded();
+    }
+
+    private byte[] getFailedOcspResponse(KeyPair ocspKp, DigestCalculatorProvider digCalcProv, X509Certificate issuerCert, X509Certificate cert)
+        throws Exception
+    {
+        BasicOCSPRespBuilder respGen = new JcaBasicOCSPRespBuilder(ocspKp.getPublic(), digCalcProv.get(RespID.HASH_SHA1));
+
+        CertificateID eeID = new CertificateID(digCalcProv.get(CertificateID.HASH_SHA1), new JcaX509CertificateHolder(issuerCert), cert.getSerialNumber());
+
+        respGen.addResponse(eeID, new RevokedStatus(new Date(), CRLReason.keyCompromise));
+
+        BasicOCSPResp resp = respGen.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BC).build(ocspKp.getPrivate()), null, new Date());
+        OCSPRespBuilder rGen = new OCSPRespBuilder();
+
+        return rGen.build(OCSPRespBuilder.UNAUTHORIZED, resp).getEncoded();
+    }
+
+    private class NonceExtension
+        implements java.security.cert.Extension
+    {
+        private final byte[] nonce;
+
+        NonceExtension(byte[] nonce)
+        {
+            this.nonce = nonce;
+        }
+
+        public String getId()
+        {
+            return OCSPObjectIdentifiers.id_pkix_ocsp_nonce.getId();
+        }
+
+        public boolean isCritical()
+        {
+            return false;
+        }
+
+        public byte[] getValue()
+        {
+            return nonce;
+        }
+
+        public void encode(OutputStream outputStream)
+            throws IOException
+        {
+            outputStream.write(new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, nonce).getEncoded());
+        }
+    }
+
+    private class OCSPResponderTask
+        implements Runnable
+    {
+        private final byte[] resp;
+        private final int portNo;
+
+        OCSPResponderTask(int portNo, byte[] resp)
+        {
+            this.portNo = portNo;
+            this.resp = resp;
+        }
+
+        public void run()
+        {
+            try
+            {
+                ServerSocket ss = new ServerSocket(portNo);
+                Socket s = ss.accept();
+
+                InputStream sIn = s.getInputStream();
+                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+                int ch;
+                int contentLength = 0;
+                while ((ch = sIn.read()) >= 0)
+                {
+                    bOut.write(ch);
+                    if (ch == '\n')
+                    {
+                        String line = Strings.fromByteArray(bOut.toByteArray()).trim();
+                        if (line.startsWith("Content-Length"))
+                        {
+                             contentLength = Integer.parseInt(line.substring("Content-Length: ".length()));
+                        }
+                        if (line.length() == 0)
+                        {
+                            break;
+                        }
+                        bOut.reset();
+                    }
+                }
+
+                byte[] request = new byte[contentLength];
+                Streams.readFully(sIn, request);
+
+                OutputStream sOut = s.getOutputStream();
+
+                sOut.write(Strings.toByteArray("HTTP/1.1 200 OK\r\n"));
+                sOut.write(Strings.toByteArray("Content-type: application/ocsp-response\r\n"));
+                sOut.write(Strings.toByteArray("Content-Length: " + resp.length + "\r\n"));
+                sOut.write(Strings.toByteArray("\r\n"));
+                sOut.write(resp);
+                sOut.flush();
+                sOut.close();
+                s.close();
+                ss.close();
+            }
+            catch (Exception e)
+            {
+                // ignore
+            }
+        }
+    }
+    public static void main(
+        String[] args)
+        throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new PKIXRevocationTest());
+    }
+}
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/selector/package.html b/bcpkix/src/main/java/org/bouncycastle/cert/selector/package.html
index c5c4211..4eaa5ad 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/selector/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/selector/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 Specialised Selector classes for certificates, CRLs, and attribute certificates.
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/test/AttrCertTest.java b/bcpkix/src/main/java/org/bouncycastle/cert/test/AttrCertTest.java
index c296354..ec42e78 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/test/AttrCertTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/test/AttrCertTest.java
@@ -628,7 +628,7 @@
         Set exts = aCert.getCriticalExtensionOIDs();
         
         if (exts.size() != 1 || !exts.contains(new ASN1ObjectIdentifier("1.1")))
-        {               System.err.println(exts);
+        {
             fail("critical extension test failed");
         }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/test/BcAttrCertTest.java b/bcpkix/src/main/java/org/bouncycastle/cert/test/BcAttrCertTest.java
index e4ca215..7314b02 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/test/BcAttrCertTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/test/BcAttrCertTest.java
@@ -587,7 +587,6 @@
         X500Name principal0 = X500Name.getInstance(RFC4519Style.INSTANCE, principals[0]);
         if (!principal0.toString().equals("c=US,o=vt,ou=Class 2,ou=Virginia Tech User,cn=Markus Lorch (mlorch),1.2.840.113549.1.9.1=mlorch@vt.edu"))
         {
-            System.err.println(principal0.toString());
             fail("principal[0] for entity names don't match");
         }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/test/BcCertTest.java b/bcpkix/src/main/java/org/bouncycastle/cert/test/BcCertTest.java
index 77d6224..8a0f08d 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/test/BcCertTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/test/BcCertTest.java
@@ -5,6 +5,7 @@
 import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 import java.security.SecureRandom;
+import java.security.Security;
 import java.security.cert.CRL;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -70,6 +71,7 @@
 import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
 import org.bouncycastle.crypto.util.PublicKeyFactory;
 import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.ContentVerifierProvider;
@@ -616,9 +618,12 @@
 
         cert = (X509Certificate)certFact.generateCertificate(bIn);
 
-        if (!cert.getKeyUsage()[7])
         {
-            fail("error generating cert - key usage wrong.");
+            boolean[] keyUsage = cert.getKeyUsage();
+            if (keyUsage == null || keyUsage.length <= 7 || !keyUsage[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
         }
 
         List l = cert.getExtendedKeyUsage();
@@ -1473,7 +1478,25 @@
 
         cert = new X509CertificateHolder(new DERSequence(v).getEncoded());
 
-        assertTrue(cert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+        try
+        {
+            System.setProperty("org.bouncycastle.x509.allow_absent_equiv_NULL", "true");
+
+            assertTrue(cert.isSignatureValid(new BcRSAContentVerifierProviderBuilder(digAlgFinder).build(pubKey)));
+        }
+        catch (Exception e)
+        {
+            fail(dump + Strings.lineSeparator() + getName() + ": testNullDerNull failed - exception " + e.toString());
+        }
+        finally
+        {
+            System.setProperty("org.bouncycastle.x509.allow_absent_equiv_NULL", "false");
+        }
+    }
+
+    public void setUp()
+    {
+        Security.addProvider(new BouncyCastleProvider());
     }
 
     public void testCertificates()
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/test/CertTest.java b/bcpkix/src/main/java/org/bouncycastle/cert/test/CertTest.java
index 2e44097..fc402f2 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/test/CertTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/test/CertTest.java
@@ -8,6 +8,7 @@
 import java.io.Serializable;
 import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
+import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -19,6 +20,7 @@
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Signature;
+import java.security.SignatureException;
 import java.security.cert.CRL;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
@@ -54,6 +56,8 @@
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.RSAPublicKey;
@@ -74,6 +78,7 @@
 import org.bouncycastle.asn1.x9.ECNamedCurveTable;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.cert.CertException;
 import org.bouncycastle.cert.X509AttributeCertificateHolder;
 import org.bouncycastle.cert.X509CRLEntryHolder;
 import org.bouncycastle.cert.X509CRLHolder;
@@ -93,6 +98,9 @@
 import org.bouncycastle.crypto.params.DSAValidationParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
+import org.bouncycastle.jcajce.CompositePrivateKey;
+import org.bouncycastle.jcajce.CompositePublicKey;
+import org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec;
 import org.bouncycastle.jce.X509KeyUsage;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
@@ -111,7 +119,11 @@
 import org.bouncycastle.operator.bc.BcRSAContentVerifierProviderBuilder;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
 import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
+import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
+import org.bouncycastle.pqc.jcajce.interfaces.XMSSPrivateKey;
 import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec;
 import org.bouncycastle.pqc.jcajce.spec.QTESLAParameterSpec;
 import org.bouncycastle.pqc.jcajce.spec.SPHINCS256KeyGenParameterSpec;
 import org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec;
@@ -1273,7 +1285,7 @@
             "/BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg" +
             "w1AH9efZBw=="
     );
-    
+
     private static byte[] gost_2012_privateKey = Base64.decode(
         "MEgCAQAwHwYIKoUDBwEBBgEwEwYHKoUDAgIkAAYIKoUDBwEBAgIEIgQg0MVlKYHb5/AwO1ZjNW8nhjyX3IgHo7nPSKuvKf87" +
             "tTU=");
@@ -1337,7 +1349,7 @@
         {
             bIn = new ByteArrayInputStream(bytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             Certificate cert = fact.generateCertificate(bIn);
 
@@ -1364,7 +1376,7 @@
         {
             bIn = new ByteArrayInputStream(bytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             Certificate cert = fact.generateCertificate(bIn);
 
@@ -1392,7 +1404,7 @@
         {
             bIn = new ByteArrayInputStream(bytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
 
@@ -1421,13 +1433,14 @@
         {
             bIn = new ByteArrayInputStream(bytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             X509Certificate cert = (X509Certificate)fact.generateCertificate(bIn);
 
             PublicKey k = cert.getPublicKey();
 
-            if (cert.getKeyUsage()[7])
+            boolean[] keyUsage = cert.getKeyUsage();
+            if (keyUsage == null || keyUsage.length <= 7 || keyUsage[7])
             {
                 fail("error generating cert - key usage wrong.");
             }
@@ -1453,15 +1466,16 @@
         {
             bIn = new ByteArrayInputStream(bytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             Certificate cert = fact.generateCertificate(bIn);
 
             PublicKey k = cert.getPublicKey();
 
             X509CertificateHolder certHldr = new X509CertificateHolder(bytes);
+            String provider = certHldr.getSignatureAlgorithm().getAlgorithm().equals(IsaraObjectIdentifiers.id_alg_xmss) ? "BCPQC" : BC;
 
-            isTrue(certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(k)));
+            isTrue(certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(provider).build(k)));
             // System.out.println(cert);
         }
         catch (Exception e)
@@ -1484,7 +1498,7 @@
         {
             bIn = new ByteArrayInputStream(certBytes);
 
-            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             Certificate cert = fact.generateCertificate(bIn);
 
@@ -1492,14 +1506,14 @@
 
             X509CertificateHolder certHldr = new X509CertificateHolder(certBytes);
 
-            isTrue(certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(k)));
+            isTrue(certHldr.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(k)));
             // System.out.println(cert);
 
-            KeyFactory keyFactory = KeyFactory.getInstance(k.getAlgorithm(), "BC");
+            KeyFactory keyFactory = KeyFactory.getInstance(k.getAlgorithm(), BC);
 
             PrivateKey privKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
 
-            Signature signer = Signature.getInstance(sigAlgorithm, "BC");
+            Signature signer = Signature.getInstance(sigAlgorithm, BC);
 
             signer.initSign(privKey);
 
@@ -1559,7 +1573,7 @@
         PrivateKey privKey;
         PublicKey pubKey;
 
-        KeyFactory fact = KeyFactory.getInstance("RSA", "BC");
+        KeyFactory fact = KeyFactory.getInstance("RSA", BC);
 
         privKey = fact.generatePrivate(privKeySpec);
         pubKey = fact.generatePublic(pubKeySpec);
@@ -1651,13 +1665,16 @@
         }
 
         ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
-        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", BC);
 
         cert = (X509Certificate)certFact.generateCertificate(bIn);
 
-        if (!cert.getKeyUsage()[7])
         {
-            fail("error generating cert - key usage wrong.");
+            boolean[] keyUsage = cert.getKeyUsage();
+            if (keyUsage == null || keyUsage.length <= 7 || !keyUsage[7])
+            {
+                fail("error generating cert - key usage wrong.");
+            }
         }
 
         List l = cert.getExtendedKeyUsage();
@@ -1696,7 +1713,7 @@
         cert.verify(cert.getPublicKey());
 
         bIn = new ByteArrayInputStream(cert.getEncoded());
-        certFact = CertificateFactory.getInstance("X.509", "BC");
+        certFact = CertificateFactory.getInstance("X.509", BC);
 
         cert = (X509Certificate)certFact.generateCertificate(bIn);
 
@@ -1876,7 +1893,7 @@
 
         try
         {
-            KeyPairGenerator g = KeyPairGenerator.getInstance("EC", "BC");
+            KeyPairGenerator g = KeyPairGenerator.getInstance("EC", BC);
 
             g.initialize(new ECNamedCurveGenParameterSpec("sm2p256v1"));
 
@@ -1931,7 +1948,7 @@
     private void checkComparison(byte[] encCert)
         throws NoSuchProviderException, CertificateException
     {
-        CertificateFactory bcFact = CertificateFactory.getInstance("X.509", "BC");
+        CertificateFactory bcFact = CertificateFactory.getInstance("X.509", BC);
         CertificateFactory sunFact = CertificateFactory.getInstance("X.509", "SUN");
 
         X509Certificate bcCert = (X509Certificate)bcFact.generateCertificate(new ByteArrayInputStream(encCert));
@@ -2443,7 +2460,7 @@
 
         X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
 
-        crl.verify(pair.getPublic(), "BC");
+        crl.verify(pair.getPublic(), BC);
 
         if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
         {
@@ -2646,7 +2663,7 @@
 
         X509CRL readCrl = (X509CRL)cFact.generateCRL(new ByteArrayInputStream(crlHolder.getEncoded()));
 
-        readCrl.verify(pair.getPublic(), "BC");
+        readCrl.verify(pair.getPublic(), BC);
 
         if (readCrl == null)
         {
@@ -2699,7 +2716,7 @@
 
         X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
 
-        crl.verify(pair.getPublic(), "BC");
+        crl.verify(pair.getPublic(), BC);
 
         if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
         {
@@ -2791,7 +2808,7 @@
 
         X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
 
-        crl.verify(pair.getPublic(), "BC");
+        crl.verify(pair.getPublic(), BC);
 
         if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
         {
@@ -2841,6 +2858,153 @@
         }
     }
 
+    public void checkCRLCompositeCreation()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
+
+        ecKpg.initialize(new ECGenParameterSpec("P-256"));
+
+        KeyPair ecKp = ecKpg.generateKeyPair();
+
+        PrivateKey ecPriv = ecKp.getPrivate();
+        PublicKey ecPub = ecKp.getPublic();
+
+        KeyPairGenerator lmsKpg = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        lmsKpg.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair lmsKp = lmsKpg.generateKeyPair();
+
+        PrivateKey lmsPriv = lmsKp.getPrivate();
+        PublicKey lmsPub = lmsKp.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder()
+            .add("SHA256withECDSA")
+            .add("LMS")
+            .build();
+        CompositePublicKey compPub = new CompositePublicKey(ecPub, lmsPub);
+        CompositePrivateKey compPrivKey = new CompositePrivateKey(ecPriv, lmsPriv);
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("Composite", compAlgSpec).build(compPrivKey);
+
+        Date now = new Date();
+
+        X509v2CRLBuilder crlGen = new JcaX509v2CRLBuilder(new X500Principal("CN=Test CA"), now);
+
+        crlGen.setNextUpdate(new Date(now.getTime() + 100000));
+
+        Vector extOids = new Vector();
+        Vector extValues = new Vector();
+
+        CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
+
+        try
+        {
+            extOids.addElement(Extension.reasonCode);
+            extValues.addElement(new Extension(Extension.reasonCode, false, new DEROctetString(crlReason.getEncoded())));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("error encoding reason: " + e);
+        }
+
+        Extensions entryExtensions = generateExtensions(extOids, extValues);
+
+        crlGen.addCRLEntry(BigInteger.ONE, now, entryExtensions);
+
+        JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
+
+        crlGen.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(ecKp.getPublic()));
+
+        X509CRLHolder crlHolder = crlGen.build(sigGen);
+
+        X509CRL crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        // comp test
+        crl.verify(compPub);
+
+        // null comp test
+        try
+        {
+            crl.verify(new CompositePublicKey(null, null));
+        }
+        catch (InvalidKeyException e)
+        {
+            isTrue(e.getMessage().equals("no matching key found"));
+        }
+        
+        // single key test
+        crl.verify(ecPub, BC);
+
+        if (!crl.getIssuerX500Principal().equals(new X500Principal("CN=Test CA")))
+        {
+            fail("failed CRL issuer test");
+        }
+
+        byte[] authExt = crl.getExtensionValue(Extension.authorityKeyIdentifier.getId());
+
+        if (authExt == null)
+        {
+            fail("failed to find CRL extension");
+        }
+
+        AuthorityKeyIdentifier authId = AuthorityKeyIdentifier.getInstance(ASN1OctetString.getInstance(authExt).getOctets());
+
+        X509CRLEntry entry = crl.getRevokedCertificate(BigInteger.ONE);
+
+        if (entry == null)
+        {
+            fail("failed to find CRL entry");
+        }
+
+        if (!entry.getSerialNumber().equals(BigInteger.ONE))
+        {
+            fail("CRL cert serial number does not match");
+        }
+
+        if (!entry.hasExtensions())
+        {
+            fail("CRL entry extension not found");
+        }
+
+        byte[] ext = entry.getExtensionValue(Extension.reasonCode.getId());
+
+        if (ext != null)
+        {
+            ASN1Enumerated reasonCode = (ASN1Enumerated)fromExtensionValue(ext);
+
+            if (reasonCode.intValueExact() != CRLReason.privilegeWithdrawn)
+            {
+                fail("CRL entry reasonCode wrong");
+            }
+        }
+        else
+        {
+            fail("CRL entry reasonCode not found");
+        }
+
+        sigGen = new JcaContentSignerBuilder("SHA256withECDSA", compAlgSpec).build(compPrivKey);
+
+        crlHolder = crlGen.build(sigGen);
+
+        crl = new JcaX509CRLConverter().setProvider(BC).getCRL(crlHolder);
+
+        // comp test - single key
+        crl.verify(compPub);
+    }
+
     /*
      * we generate a self signed certificate for the sake of testing - GOST3410
      */
@@ -3178,8 +3342,8 @@
         //
         // create the certificate - version 3
         //
-        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withXMSS").setProvider("BCPQC").build(privKey);
-        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
+        ContentSigner sigGen = new JcaContentSignerBuilder("XMSS").setProvider("BCPQC").build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(((XMSSPrivateKey)privKey).getIndex()), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
 
         X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
 
@@ -3214,7 +3378,7 @@
         //
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
 
-        kpg.initialize(new XMSSMTParameterSpec(10, 5, XMSSMTParameterSpec.SHAKE256), new SecureRandom());
+        kpg.initialize(new XMSSMTParameterSpec(20, 4, XMSSMTParameterSpec.SHAKE256), new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
@@ -3229,7 +3393,58 @@
         //
         // create the certificate - version 3
         //
-        ContentSigner sigGen = new JcaContentSignerBuilder("SHAKE256withXMSSMT").setProvider("BCPQC").build(privKey);
+        ContentSigner sigGen = new JcaContentSignerBuilder("XMSSMT").setProvider("BCPQC").build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(pubKey);
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        //System.out.println(cert);
+    }
+
+    /*
+     * we generate a self signed certificate for the sake of testing - LMS
+     */
+    public void checkCreation10()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpg.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        PrivateKey privKey = kp.getPrivate();
+        PublicKey pubKey = kp.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("LMS").setProvider("BCPQC").build(privKey);
         X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
 
         X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
@@ -3314,7 +3529,7 @@
         //
         // set up the keys
         //
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", "BC");
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA", BC);
 
         kpg.initialize(new DSAParameterSpec(def2048Params.getP(), def2048Params.getQ(), def2048Params.getG()), new SecureRandom());
 
@@ -3363,7 +3578,7 @@
         //
         // set up the keys
         //
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BC);
 
         kpg.initialize(2048, new SecureRandom());
 
@@ -3443,7 +3658,7 @@
         //
         ContentSigner sigGen = new JcaContentSignerBuilder("RSAPSS",
             new PSSParameterSpec("SHA-256", "MGF1", new MGF1ParameterSpec("SHA-256"), 20, 1))
-            .setProvider("BC").build(privKey);
+            .setProvider(BC).build(privKey);
         X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
 
         X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
@@ -3468,6 +3683,55 @@
         //System.out.println(cert);
     }
 
+    public void checkCreationRSAPSS()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSASSA-PSS", "BC");
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        PrivateKey privKey = kp.getPrivate();
+        PublicKey pubKey = kp.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        //
+        // create the certificate - version 3
+        //
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withRSAandMGF1").setProvider("BC").build(privKey);
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(pubKey);
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        org.bouncycastle.asn1.x509.Certificate crt = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded());
+
+        isTrue(PKCSObjectIdentifiers.id_RSASSA_PSS.equals(crt.getSubjectPublicKeyInfo().getAlgorithm().getAlgorithm()));
+        isTrue(null == crt.getSubjectPublicKeyInfo().getAlgorithm().getParameters());
+    }
+
     /*
      * we generate a self signed certificate across the range of ECDSA algorithms
      */
@@ -3477,7 +3741,7 @@
         //
         // set up the keys
         //
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BC);
 
         kpg.initialize(256, new SecureRandom());
 
@@ -3522,6 +3786,242 @@
         doGenSelfSignedCert(privKey, pubKey, algs, oids);
     }
 
+    public void checkCreationComposite()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
+
+        ecKpg.initialize(new ECGenParameterSpec("P-256"));
+
+        KeyPair ecKp = ecKpg.generateKeyPair();
+
+        PrivateKey ecPriv = ecKp.getPrivate();
+        PublicKey ecPub = ecKp.getPublic();
+
+        KeyPairGenerator lmsKpg = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        lmsKpg.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair lmsKp = lmsKpg.generateKeyPair();
+
+        PrivateKey lmsPriv = lmsKp.getPrivate();
+        PublicKey lmsPub = lmsKp.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        X500Name issuer = builder.build();
+
+        //
+        // create the certificate - version 3
+        //
+        CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder()
+            .add("SHA256withECDSA")
+            .add("LMS")
+            .build();
+        CompositePublicKey compPub = new CompositePublicKey(ecPub, lmsPub);
+        CompositePrivateKey compPrivKey = new CompositePrivateKey(ecPriv, lmsPriv);
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("Composite", compAlgSpec).build(compPrivKey);
+
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            issuer,
+            BigInteger.valueOf(1),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), issuer,
+            compPub);
+
+        X509CertificateHolder certHldr = certGen.build(sigGen);
+
+        ContentVerifierProvider vProv = new JcaContentVerifierProviderBuilder()
+            .build(compPub);
+
+        isTrue("multi failed", certHldr.isSignatureValid(vProv));
+
+        vProv = new JcaContentVerifierProviderBuilder()
+            .build(new CompositePublicKey(ecPub, null));
+
+        isTrue("multi failed with null", certHldr.isSignatureValid(vProv));
+
+
+        try
+        {
+            vProv = new JcaContentVerifierProviderBuilder()
+                .build(new CompositePublicKey(null, null));
+
+            certHldr.isSignatureValid(vProv);
+        }
+        catch (CertException e)
+        {
+            isTrue(e.getCause().getMessage().equals("no matching signature found in composite"));
+        }
+
+        vProv = new JcaContentVerifierProviderBuilder().build(ecPub);
+
+        isTrue("ec failed", certHldr.isSignatureValid(vProv));
+
+        vProv = new JcaContentVerifierProviderBuilder().build(lmsPub);
+
+        isTrue("lms failed", certHldr.isSignatureValid(vProv));
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certHldr);
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(compPub);
+
+        // null comp test
+        try
+        {
+            cert.verify(new CompositePublicKey(null, null));
+        }
+        catch (InvalidKeyException e)
+        {
+            isTrue(e.getMessage(), e.getMessage().equals("no matching key found"));
+        }
+
+        cert.verify(ecPub);      // ec key only
+
+        cert.verify(lmsPub);     // lms key only
+
+        cert.verify(ecPub, "BC");      // ec key only
+
+        cert.verify(lmsPub, "BCPQC");     // lms key only
+
+        cert.verify(ecPub, new BouncyCastleProvider());      // ec key only
+
+        cert.verify(lmsPub, new BouncyCastlePQCProvider());     // lms key only
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        org.bouncycastle.asn1.x509.Certificate crt = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded());
+
+        isTrue(MiscObjectIdentifiers.id_alg_composite.equals(crt.getSubjectPublicKeyInfo().getAlgorithm().getAlgorithm()));
+        isTrue(null == crt.getSubjectPublicKeyInfo().getAlgorithm().getParameters());
+
+        KeyFactory kFact = KeyFactory.getInstance("Composite", "BC");
+
+        CompositePublicKey pubKey = (CompositePublicKey)kFact.generatePublic(new X509EncodedKeySpec(compPub.getEncoded()));
+        CompositePrivateKey privKey = (CompositePrivateKey)kFact.generatePrivate(new PKCS8EncodedKeySpec(compPrivKey.getEncoded()));
+
+        isTrue(pubKey.equals(compPub));
+        isTrue(privKey.equals(compPrivKey));
+    }
+
+    private void checkCompositeCertificateVerify()
+        throws Exception
+    {
+        //
+        // set up the keys
+        //
+        KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
+
+        ecKpg.initialize(new ECGenParameterSpec("P-256"));
+
+        KeyPair ecKp = ecKpg.generateKeyPair();
+
+        PrivateKey ecPriv = ecKp.getPrivate();
+        PublicKey ecPub = ecKp.getPublic();
+
+        KeyPairGenerator lmsKpg = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        lmsKpg.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair lmsKp = lmsKpg.generateKeyPair();
+
+        PrivateKey lmsPriv = lmsKp.getPrivate();
+        PublicKey lmsPub = lmsKp.getPublic();
+
+        //
+        // distinguished name table.
+        //
+        X500NameBuilder builder = createStdBuilder();
+
+        X500Name issuer = builder.build();
+
+        //
+        // create the certificate - version 3
+        //
+        CompositeAlgorithmSpec compAlgSpec = new CompositeAlgorithmSpec.Builder()
+            .add("SHA256withECDSA")
+            .add("LMS")
+            .build();
+        CompositePublicKey compPub = new CompositePublicKey(ecPub, lmsPub);
+        CompositePrivateKey compPrivKey = new CompositePrivateKey(ecPriv, lmsPriv);
+
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256withECDSA").build(ecPriv);
+
+        X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
+            issuer,
+            BigInteger.valueOf(1),
+            new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), issuer,
+            compPub);
+
+        X509CertificateHolder ecCertHldr = certGen.build(sigGen);
+
+        ContentVerifierProvider vProv = new JcaContentVerifierProviderBuilder()
+            .build(compPub);
+
+        isTrue("ec multi failed", ecCertHldr.isSignatureValid(vProv));
+
+        vProv = new JcaContentVerifierProviderBuilder().build(ecPub);
+
+        isTrue("ec failed", ecCertHldr.isSignatureValid(vProv));
+
+        X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(ecCertHldr);
+
+        cert.checkValidity(new Date());
+
+        //
+        // check verifies in general
+        //
+        cert.verify(compPub);
+
+        cert.verify(ecPub);      // ec key only
+
+        cert.verify(ecPub, "BC");      // ec key only
+
+        cert.verify(ecPub, new BouncyCastleProvider());      // ec key only
+
+        //
+        // check verifies with contained key
+        //
+        cert.verify(cert.getPublicKey());
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
+        CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
+
+        cert = (X509Certificate)fact.generateCertificate(bIn);
+
+        org.bouncycastle.asn1.x509.Certificate crt = org.bouncycastle.asn1.x509.Certificate.getInstance(cert.getEncoded());
+
+        isTrue(MiscObjectIdentifiers.id_alg_composite.equals(crt.getSubjectPublicKeyInfo().getAlgorithm().getAlgorithm()));
+        isTrue(null == crt.getSubjectPublicKeyInfo().getAlgorithm().getParameters());
+
+        KeyFactory kFact = KeyFactory.getInstance("Composite", "BC");
+
+        CompositePublicKey pubKey = (CompositePublicKey)kFact.generatePublic(new X509EncodedKeySpec(compPub.getEncoded()));
+        CompositePrivateKey privKey = (CompositePrivateKey)kFact.generatePrivate(new PKCS8EncodedKeySpec(compPrivKey.getEncoded()));
+
+        isTrue(pubKey.equals(compPub));
+        isTrue(privKey.equals(compPrivKey));
+    }
+
     private void doGenSelfSignedCert(PrivateKey privKey, PublicKey pubKey, String[] algs, ASN1ObjectIdentifier[] oids)
         throws Exception
     {
@@ -3532,7 +4032,7 @@
             //
             // create the certificate - version 3
             //
-            ContentSigner sigGen = new JcaContentSignerBuilder(algs[i]).setProvider("BC").build(privKey);
+            ContentSigner sigGen = new JcaContentSignerBuilder(algs[i]).setProvider(BC).build(privKey);
             X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(builder.build(), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), builder.build(), pubKey);
 
             isEquals("oid mismatch", sigGen.getAlgorithmIdentifier().getAlgorithm(), oids[i]);
@@ -3555,7 +4055,6 @@
             CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
 
             cert = (X509Certificate)fact.generateCertificate(bIn);
-
 //            System.out.println(cert);
         }
     }
@@ -3802,7 +4301,7 @@
         PublicKey pubKey = pair.getPublic();
         PrivateKey privKey = pair.getPrivate();
 
-        ContentSigner sigGen = new JcaContentSignerBuilder("MD5WithRSAEncryption").setProvider(BC).build(privKey);
+        ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(privKey);
         JcaX509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(new X500Name("CN=Test"), BigInteger.valueOf(1), new Date(System.currentTimeMillis() - 50000), new Date(System.currentTimeMillis() + 50000), new X500Name("CN=Test"), pubKey);
         X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
 
@@ -3829,18 +4328,52 @@
 
             cert = (X509Certificate)fact.generateCertificate(bIn);
 
+            try
+            {
+                cert.verify(cert.getPublicKey());
+                fail("no exception - X509Cert");
+            }
+            catch (CertificateException e)
+            {
+                isTrue(e.getMessage().equals("signature algorithm in TBS cert not same as outer cert"));
+            }
+
+            try
+            {
+                X509CertificateHolder x509CertHldr = new JcaX509CertificateHolder(cert);
+
+                x509CertHldr.isSignatureValid(new JcaContentVerifierProviderBuilder()
+                    .setProvider("BC").build(cert));
+                fail("no exception - CertHolder");
+            }
+            catch (CertException e)
+            {
+                isTrue(e.getMessage().equals("signature invalid - algorithm identifier mismatch"));
+            }
+
+            System.setProperty("org.bouncycastle.x509.allow_absent_equiv_NULL", "true");
+
             cert.verify(cert.getPublicKey());
+
+            X509CertificateHolder x509CertHldr = new JcaX509CertificateHolder(cert);
+
+            x509CertHldr.isSignatureValid(new JcaContentVerifierProviderBuilder()
+                .setProvider("BC").build(cert));
         }
         catch (Exception e)
         {
             fail(dump + Strings.lineSeparator() + getName() + ": testNullDerNull failed - exception " + e.toString(), e);
         }
+        finally
+        {
+            System.setProperty("org.bouncycastle.x509.allow_absent_equiv_NULL", "false");
+        }
     }
 
     private void testDirect()
         throws Exception
     {
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
 
@@ -3857,11 +4390,11 @@
 
         JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
 
-        contentSignerBuilder.setProvider("BC");
+        contentSignerBuilder.setProvider(BC);
 
         X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
 
-        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(certificate)))
         {
             fail("CRL signature not valid");
         }
@@ -3875,7 +4408,7 @@
 
         JcaX509CRLConverter converter = new JcaX509CRLConverter();
 
-        converter.setProvider("BC");
+        converter.setProvider(BC);
 
         X509CRL crl = converter.getCRL(cRLHolder);
 
@@ -3903,7 +4436,7 @@
     private void testIndirect()
         throws Exception
     {
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
 
@@ -3928,11 +4461,11 @@
 
         JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
 
-        contentSignerBuilder.setProvider("BC");
+        contentSignerBuilder.setProvider(BC);
 
         X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
 
-        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(certificate)))
         {
             fail("CRL signature not valid");
         }
@@ -3946,7 +4479,7 @@
 
         JcaX509CRLConverter converter = new JcaX509CRLConverter();
 
-        converter.setProvider("BC");
+        converter.setProvider(BC);
 
         X509CRL crl = converter.getCRL(cRLHolder);
 
@@ -3974,7 +4507,7 @@
     private void testIndirect2()
         throws Exception
     {
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
 
@@ -4004,11 +4537,11 @@
 
         JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
 
-        contentSignerBuilder.setProvider("BC");
+        contentSignerBuilder.setProvider(BC);
 
         X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
 
-        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(certificate)))
         {
             fail("CRL signature not valid");
         }
@@ -4036,7 +4569,7 @@
 
         JcaX509CRLConverter converter = new JcaX509CRLConverter();
 
-        converter.setProvider("BC");
+        converter.setProvider(BC);
 
         X509CRL crl = converter.getCRL(cRLHolder);
 
@@ -4060,7 +4593,7 @@
     private void testMalformedIndirect()
         throws Exception
     {
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         ByteArrayInputStream input = new ByteArrayInputStream(testCAp12);
 
@@ -4083,11 +4616,11 @@
 
         JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder("SHA256WithRSAEncryption");
 
-        contentSignerBuilder.setProvider("BC");
+        contentSignerBuilder.setProvider(BC);
 
         X509CRLHolder cRLHolder = builder.build(contentSignerBuilder.build(privateKey));
 
-        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(certificate)))
+        if (!cRLHolder.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider(BC).build(certificate)))
         {
             fail("CRL signature not valid");
         }
@@ -4101,7 +4634,7 @@
 
         JcaX509CRLConverter converter = new JcaX509CRLConverter();
 
-        converter.setProvider("BC");
+        converter.setProvider(BC);
 
         X509CRL crl = converter.getCRL(cRLHolder);
 
@@ -4218,9 +4751,30 @@
         checkSelfSignedCertificateAndKey(20, gost_2012_cert, "ECGOST3410-2012-256", gost_2012_privateKey);
         checkCRL(1, crl1);
 
-        checkCertificate(21, x25519Cert,
+        System.setProperty("org.bouncycastle.x509.allow_non-der_tbscert", "true");
+
+        checkCertificate(9, x25519Cert,
             KeyFactory.getInstance("EdDSA").generatePublic(new X509EncodedKeySpec(Base64.decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE="))));
 
+        System.setProperty("org.bouncycastle.x509.allow_non-der_tbscert", "false");
+
+        try
+        {
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(new ByteArrayInputStream(x25519Cert));
+
+            cert.verify(KeyFactory.getInstance("EdDSA").generatePublic(new X509EncodedKeySpec(Base64.decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE="))));
+
+            fail("no exception");
+        }
+        catch (SignatureException e)
+        {
+            isEquals("certificate does not verify with supplied key", e.getMessage());
+        }
+
+        checkSelfSignedCertificate(22, CertificateFactory.getInstance("X.509", BC).generateCertificate(this.getClass().getResourceAsStream("xmss3.pem")).getEncoded());
+
         checkCreation1();
         checkCreation2();
         checkCreation3();
@@ -4231,6 +4785,7 @@
         checkCreation7();
         checkCreation8();
         checkCreation9();
+        checkCreation10();
 
         checkCreationEd448();
 
@@ -4238,9 +4793,13 @@
         checkCreationDSA();
         checkCreationECDSA();
         checkCreationRSA();
+        checkCreationRSAPSS();
 
         checkSm3WithSm2Creation();
 
+        checkCreationComposite();
+        checkCompositeCertificateVerify();
+
         createECCert("SHA1withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA1);
         createECCert("SHA224withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA224);
         createECCert("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256);
@@ -4257,6 +4816,7 @@
         checkCRLCreation3();
         checkCRLCreation4();
         checkCRLCreation5();
+        checkCRLCompositeCreation();
 
         pemTest();
         pkcs7Test();
diff --git a/bcpkix/src/main/java/org/bouncycastle/cert/test/PKCS10Test.java b/bcpkix/src/main/java/org/bouncycastle/cert/test/PKCS10Test.java
index 135716c..2ea4ef7 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cert/test/PKCS10Test.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cert/test/PKCS10Test.java
@@ -1,5 +1,6 @@
 package org.bouncycastle.cert.test;
 
+import java.io.IOException;
 import java.math.BigInteger;
 import java.security.KeyFactory;
 import java.security.KeyPair;
@@ -18,6 +19,7 @@
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.Attribute;
+import org.bouncycastle.asn1.pkcs.CertificationRequest;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.X500NameBuilder;
@@ -261,6 +263,27 @@
         {
             fail("signature not mapped correctly.");
         }
+
+        // empty tests
+        try
+        {
+            new PKCS10CertificationRequest(new byte[0]);
+            fail("no exception");
+        }
+        catch (IOException e)
+        {
+            isEquals("empty data passed to constructor", e.getMessage());
+        }
+
+        try
+        {
+            new PKCS10CertificationRequest((CertificationRequest)null);
+            fail("no exception");
+        }
+        catch (NullPointerException e)
+        {
+            isEquals("certificationRequest cannot be null", e.getMessage());
+        }
     }
 
     private void createECRequest(String algorithm, ASN1ObjectIdentifier algOid)
@@ -481,12 +504,12 @@
         }
 
         Attribute[] attr1 = p1.getAttributes();
-        Attribute[] attr2 = p1.getAttributes();
+        Attribute[] attr2 = p2.getAttributes();
 
         checkAttrs(1, attr1, attr2);
 
         attr1 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
-        attr2 = p1.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
+        attr2 = p2.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
 
         checkAttrs(1, attr1, attr2);
     }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java b/bcpkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java
index 65675e4..d13f83d 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java
@@ -11,11 +11,13 @@
 import org.bouncycastle.asn1.cms.EncryptedContentInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Encodable;
 
 /**
  * containing class for an CMS AuthEnveloped Data object
  */
 public class CMSAuthEnvelopedData
+    implements Encodable
 {
     RecipientInformationStore recipientInfoStore;
     ContentInfo contentInfo;
@@ -154,4 +156,21 @@
     {
         return Arrays.clone(mac);
     }
+
+    /**
+     * return the ContentInfo
+     */
+    public ContentInfo toASN1Structure()
+    {
+        return contentInfo;
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object.
+     */
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return contentInfo.getEncoded();
+    }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/CMSPatchKit.java b/bcpkix/src/main/java/org/bouncycastle/cms/CMSPatchKit.java
new file mode 100644
index 0000000..1237988
--- /dev/null
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/CMSPatchKit.java
@@ -0,0 +1,71 @@
+package org.bouncycastle.cms;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * Toolkit methods for dealing with common errors in CMS
+ * classes.
+ */
+public class CMSPatchKit
+{
+    /**
+     * Create a SignerInformation based on original which uses definite-length
+     * rather than DER encoding for verifying the signature on the signed attributes.
+     *
+     * @param original the source SignerInformation
+     */
+    public static SignerInformation createNonDERSignerInfo(
+        SignerInformation original)
+    {
+        return new DLSignerInformation(original);
+    }
+
+    /**
+     * Create a SignerInformation based on original has it's signatureAlgorithm replaced
+     * with the passed in AlgorithmIdentifier.
+     *
+     * @param original the source SignerInformation
+     */
+    public static SignerInformation createWithSignatureAlgorithm(
+        SignerInformation original,
+        AlgorithmIdentifier signatureAlgorithm)
+    {
+         return new ModEncAlgSignerInformation(original, signatureAlgorithm);
+    }
+
+    private static class DLSignerInformation
+        extends SignerInformation
+    {
+        protected DLSignerInformation(SignerInformation baseInfo)
+        {
+            super(baseInfo);
+        }
+
+        public byte[] getEncodedSignedAttributes()
+            throws IOException
+        {
+            return signedAttributeSet.getEncoded(ASN1Encoding.DL);
+        }
+    }
+
+    private static class ModEncAlgSignerInformation
+        extends SignerInformation
+    {
+        protected ModEncAlgSignerInformation(
+            SignerInformation baseInfo,
+            AlgorithmIdentifier signatureAlgorithm)
+        {
+            super(baseInfo, editEncAlg(baseInfo.info, signatureAlgorithm));
+        }
+
+        private static SignerInfo editEncAlg(SignerInfo info, AlgorithmIdentifier signatureAlgorithm)
+        {
+            return new SignerInfo(info.getSID(), info.getDigestAlgorithm(), info.getAuthenticatedAttributes(),
+                signatureAlgorithm, info.getEncryptedDigest(), info.getUnauthenticatedAttributes());
+        }
+    }
+}
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/CMSSignedData.java b/bcpkix/src/main/java/org/bouncycastle/cms/CMSSignedData.java
index f818e7c..132e341 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/CMSSignedData.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/CMSSignedData.java
@@ -22,6 +22,7 @@
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DLSet;
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.SignedData;
 import org.bouncycastle.asn1.cms.SignerInfo;
@@ -384,6 +385,17 @@
     }
 
     /**
+     * return the ASN.1 encoded representation of this object using the specified encoding.
+     *
+     * @param encoding the ASN.1 encoding format to use ("BER", "DL", or "DER").
+     */
+    public byte[] getEncoded(String encoding)
+        throws IOException
+    {
+        return contentInfo.getEncoded(encoding);
+    }
+
+    /**
      * Verify all the SignerInformation objects and their associated counter signatures attached
      * to this CMS SignedData object.
      *
@@ -507,7 +519,7 @@
         }
 
         ASN1Set             digests = new DERSet(digestAlgs);
-        ASN1Set             signers = new DERSet(vec);
+        ASN1Set             signers = new DLSet(vec);
         ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
 
         vec = new ASN1EncodableVector();
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/CMSTypedStream.java b/bcpkix/src/main/java/org/bouncycastle/cms/CMSTypedStream.java
index 78379a5..79ae975 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/CMSTypedStream.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/CMSTypedStream.java
@@ -86,6 +86,11 @@
 
         public int read(byte[] buf, int off, int len) throws IOException
         {
+            if (len == 0)
+            {
+                return 0;
+            }
+
             int totalRead = Streams.readFully(super.in, buf, off, len);
             return totalRead > 0 ? totalRead : -1;
         }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/CMSUtils.java b/bcpkix/src/main/java/org/bouncycastle/cms/CMSUtils.java
index 9204b5b..2bf1d19 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/CMSUtils.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/CMSUtils.java
@@ -236,7 +236,7 @@
         {
             OCSPResponse resp = OCSPResponse.getInstance(infoFormat.getInfo());
 
-            if (resp.getResponseStatus().getValue().intValue() != OCSPResponseStatus.SUCCESSFUL)
+            if (OCSPResponseStatus.SUCCESSFUL != resp.getResponseStatus().getIntValue())
             {
                 throw new IllegalArgumentException("cannot add unsuccessful OCSP response to CMS SignedData");
             }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java b/bcpkix/src/main/java/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java
index 3a3d240..77021a9 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/DefaultCMSSignatureAlgorithmNameGenerator.java
@@ -7,6 +7,7 @@
 import org.bouncycastle.asn1.bsi.BSIObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -194,6 +195,15 @@
 
     public String getSignatureName(AlgorithmIdentifier digestAlg, AlgorithmIdentifier encryptionAlg)
     {
+        if (EdECObjectIdentifiers.id_Ed25519.equals(encryptionAlg.getAlgorithm()))
+        {
+            return "Ed25519";
+        }
+        if (EdECObjectIdentifiers.id_Ed448.equals(encryptionAlg.getAlgorithm()))
+        {
+            return "Ed448";
+        }
+
         String digestName = getDigestAlgName(encryptionAlg.getAlgorithm());
 
         if (!digestName.equals(encryptionAlg.getAlgorithm().getId()))
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/SignerInfoGenerator.java b/bcpkix/src/main/java/org/bouncycastle/cms/SignerInfoGenerator.java
index e1b0ce1..692abc1 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/SignerInfoGenerator.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/SignerInfoGenerator.java
@@ -14,6 +14,8 @@
 import org.bouncycastle.asn1.cms.AttributeTable;
 import org.bouncycastle.asn1.cms.SignerIdentifier;
 import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.operator.ContentSigner;
@@ -230,6 +232,15 @@
                 unsignedAttr = getAttributeSet(unsigned);
             }
 
+            if (sAttrGen == null)
+            {
+                // RFC 8419, Section 3.2 - needs to be shake-256, not shake-256-len
+                if (EdECObjectIdentifiers.id_Ed448.equals(digestEncryptionAlgorithm.getAlgorithm()))
+                {
+                    digestAlg = new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256);
+                }
+            }
+            
             return new SignerInfo(signerIdentifier, digestAlg,
                 signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr);
         }
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/SignerInformation.java b/bcpkix/src/main/java/org/bouncycastle/cms/SignerInformation.java
index b3783be..96440a5 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/SignerInformation.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/SignerInformation.java
@@ -94,13 +94,26 @@
     /**
      * Protected constructor. In some cases clients have their own idea about how to encode
      * the signed attributes and calculate the signature. This constructor is to allow developers
-     * to deal with that by extending off the class and overridng methods like getSignedAttributes().
+     * to deal with that by extending off the class and overriding methods like getSignedAttributes().
      *
      * @param baseInfo the SignerInformation to base this one on.
      */
     protected SignerInformation(SignerInformation baseInfo)
     {
-        this.info = baseInfo.info;
+        this(baseInfo, baseInfo.info);
+    }
+
+    /**
+     * Protected constructor. In some cases clients also have their own ideas about what
+     * goes in various SignerInfo fields. This constructor is to allow developers to deal with
+     * that by also tweaking the SignerInfo so that these issues can be dealt with.
+     *
+     * @param baseInfo the SignerInformation to base this one on.
+     * @param info the SignerInfo to associate with the existing baseInfo data.
+     */
+    protected SignerInformation(SignerInformation baseInfo, SignerInfo info)
+    {
+        this.info = info;
         this.contentType = baseInfo.contentType;
         this.isCounterSignature = baseInfo.isCounterSignature();
         this.sid = baseInfo.getSID();
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
index 6c7465f..a24fd1e 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/bc/BcCMSContentEncryptorBuilder.java
@@ -3,52 +3,25 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.security.SecureRandom;
-import java.util.HashMap;
-import java.util.Map;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.cms.CMSAlgorithm;
 import org.bouncycastle.cms.CMSException;
 import org.bouncycastle.crypto.CipherKeyGenerator;
 import org.bouncycastle.crypto.modes.AEADBlockCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.util.CipherFactory;
+import org.bouncycastle.operator.DefaultSecretKeySizeProvider;
 import org.bouncycastle.operator.GenericKey;
 import org.bouncycastle.operator.OutputAEADEncryptor;
 import org.bouncycastle.operator.OutputEncryptor;
-import org.bouncycastle.util.Integers;
+import org.bouncycastle.operator.SecretKeySizeProvider;
 
 public class BcCMSContentEncryptorBuilder
 {
-    private static Map keySizes = new HashMap();
-
-    static
-    {
-        keySizes.put(CMSAlgorithm.AES128_CBC, Integers.valueOf(128));
-        keySizes.put(CMSAlgorithm.AES192_CBC, Integers.valueOf(192));
-        keySizes.put(CMSAlgorithm.AES256_CBC, Integers.valueOf(256));
-
-        keySizes.put(CMSAlgorithm.AES128_GCM, Integers.valueOf(128));
-        keySizes.put(CMSAlgorithm.AES192_GCM, Integers.valueOf(192));
-        keySizes.put(CMSAlgorithm.AES256_GCM, Integers.valueOf(256));
-
-        keySizes.put(CMSAlgorithm.CAMELLIA128_CBC, Integers.valueOf(128));
-        keySizes.put(CMSAlgorithm.CAMELLIA192_CBC, Integers.valueOf(192));
-        keySizes.put(CMSAlgorithm.CAMELLIA256_CBC, Integers.valueOf(256));
-    }
-
-    private static int getKeySize(ASN1ObjectIdentifier oid)
-    {
-        Integer size = (Integer)keySizes.get(oid);
-
-        if (size != null)
-        {
-            return size.intValue();
-        }
-
-        return -1;
-    }
+    private static final SecretKeySizeProvider KEY_SIZE_PROVIDER = DefaultSecretKeySizeProvider.INSTANCE;
 
     private final ASN1ObjectIdentifier encryptionOID;
     private final int keySize;
@@ -58,13 +31,38 @@
 
     public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID)
     {
-        this(encryptionOID, getKeySize(encryptionOID));
+        this(encryptionOID, KEY_SIZE_PROVIDER.getKeySize(encryptionOID));
     }
 
     public BcCMSContentEncryptorBuilder(ASN1ObjectIdentifier encryptionOID, int keySize)
     {
         this.encryptionOID = encryptionOID;
-        this.keySize = keySize;
+        int fixedSize = KEY_SIZE_PROVIDER.getKeySize(encryptionOID);
+
+        if (encryptionOID.equals(PKCSObjectIdentifiers.des_EDE3_CBC))
+        {
+            if (keySize != 168 && keySize != fixedSize)
+            {
+                throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
+            }
+            this.keySize = 168;
+        }
+        else if (encryptionOID.equals(OIWObjectIdentifiers.desCBC))
+        {
+            if (keySize != 56 && keySize != fixedSize)
+            {
+                throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
+            }
+            this.keySize = 56;
+        }
+        else
+        {
+            if (fixedSize > 0 && fixedSize != keySize)
+            {
+                throw new IllegalArgumentException("incorrect keySize for encryptionOID passed to builder.");
+            }
+            this.keySize = keySize;
+        }
     }
 
     public BcCMSContentEncryptorBuilder setSecureRandom(SecureRandom random)
@@ -99,7 +97,7 @@
                 random = new SecureRandom();
             }
 
-            CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, random);
+            CipherKeyGenerator keyGen = helper.createKeyGenerator(encryptionOID, keySize, random);
 
             encKey = new KeyParameter(keyGen.generateKey());
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/bc/EnvelopedDataHelper.java b/bcpkix/src/main/java/org/bouncycastle/cms/bc/EnvelopedDataHelper.java
index 0c714a1..36c6d4f 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/bc/EnvelopedDataHelper.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/bc/EnvelopedDataHelper.java
@@ -1,5 +1,10 @@
 package org.bouncycastle.cms.bc;
 
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -28,11 +33,6 @@
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.bc.BcDigestProvider;
 
-import java.security.SecureRandom;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
 class EnvelopedDataHelper
 {
     protected static final Map BASE_CIPHER_NAMES = new HashMap();
@@ -160,7 +160,8 @@
         }
     }
 
-    CipherKeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm, SecureRandom random)
+    // TODO: make use of keySize parameter.
+    CipherKeyGenerator createKeyGenerator(ASN1ObjectIdentifier algorithm, int keySize, SecureRandom random)
         throws CMSException
     {
         try
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
index 64c3117..c30c350 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceCMSContentEncryptorBuilder.java
@@ -180,10 +180,7 @@
         {
             KeyGenerator keyGen = helper.createKeyGenerator(encryptionOID);
 
-            if (random == null)
-            {
-                random = CryptoServicesRegistrar.getSecureRandom();
-            }
+            random = CryptoServicesRegistrar.getSecureRandom(random);
 
             if (keySize < 0)
             {
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java b/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
index 5f038fc..6faa159 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/jcajce/JceKeyAgreeRecipient.java
@@ -57,7 +57,7 @@
     protected EnvelopedDataHelper helper = new EnvelopedDataHelper(new DefaultJcaJceExtHelper());
     protected EnvelopedDataHelper contentHelper = helper;
     private SecretKeySizeProvider keySizeProvider = new DefaultSecretKeySizeProvider();
-
+    private AlgorithmIdentifier privKeyAlgID = null;
 
     public JceKeyAgreeRecipient(PrivateKey recipientKey)
     {
@@ -120,6 +120,20 @@
         return this;
     }
 
+    /**
+     * Set the algorithm identifier for the private key. You'll want to use this if you are
+     * dealing with a HSM and it is not possible to get the encoding of the private key.
+     *
+     * @param privKeyAlgID the name of the provider to use.
+     * @return this recipient.
+     */
+    public JceKeyAgreeRecipient setPrivateKeyAlgorithmIdentifier(AlgorithmIdentifier privKeyAlgID)
+    {
+        this.privKeyAlgID = privKeyAlgID;
+
+        return this;
+    }
+
     private SecretKey calculateAgreedWrapKey(AlgorithmIdentifier keyEncAlg, AlgorithmIdentifier wrapAlg,
         PublicKey senderPublicKey, ASN1OctetString userKeyingMaterial, PrivateKey receiverPrivateKey, KeyMaterialGenerator kmGen)
         throws CMSException, GeneralSecurityException, IOException
@@ -276,7 +290,12 @@
 
     public AlgorithmIdentifier getPrivateKeyAlgorithmIdentifier()
     {
-        return PrivateKeyInfo.getInstance(recipientKey.getEncoded()).getPrivateKeyAlgorithm();
+        if (privKeyAlgID == null)
+        {
+            privKeyAlgID = PrivateKeyInfo.getInstance(recipientKey.getEncoded()).getPrivateKeyAlgorithm();
+        }
+
+        return privKeyAlgID;
     }
 
     private static KeyMaterialGenerator old_ecc_cms_Generator = new KeyMaterialGenerator()
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/AuthEnvelopedDataTest.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/AuthEnvelopedDataTest.java
index c0abdf0..fb99b85 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/AuthEnvelopedDataTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/AuthEnvelopedDataTest.java
@@ -35,6 +35,7 @@
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.operator.OutputAEADEncryptor;
 import org.bouncycastle.operator.OutputEncryptor;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Base64;
 
@@ -149,6 +150,8 @@
         byte[] recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(privKey).setProvider(BC));
 
         assertEquals("auth-enveloped data", Strings.fromByteArray(recData));
+
+        assertTrue(Arrays.areEqual(Sample1, authEnv.getEncoded()));
     }
 
     public void testAttributes()
@@ -183,7 +186,9 @@
         
         CMSAuthEnvelopedData authData = authGen.generate(new CMSProcessableByteArray(message), macProvider);
 
-        RecipientInformationStore recipients = authData.getRecipientInfos();
+        CMSAuthEnvelopedData encAuthData = new CMSAuthEnvelopedData(authData.getEncoded());
+
+        RecipientInformationStore recipients = encAuthData.getRecipientInfos();
 
         RecipientInformation recipient = (RecipientInformation)recipients.getRecipients().iterator().next();
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/CMSTestUtil.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/CMSTestUtil.java
index 01cccf3..0b43b9f 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/CMSTestUtil.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/CMSTestUtil.java
@@ -58,6 +58,8 @@
     public static KeyPairGenerator dhKpg;
     public static KeyPairGenerator ecGostKpg;
     public static KeyPairGenerator ecDsaKpg;
+    public static KeyPairGenerator ed25519Kpg;
+    public static KeyPairGenerator ed448Kpg;
     public static KeyGenerator     aes192kg;
     public static KeyGenerator     desede128kg;
     public static KeyGenerator     desede192kg;
@@ -148,6 +150,9 @@
             ecDsaKpg = KeyPairGenerator.getInstance("ECDSA", "BC");
             ecDsaKpg.initialize(239, new SecureRandom());
 
+            ed25519Kpg = KeyPairGenerator.getInstance("Ed25519", "BC");
+            ed448Kpg = KeyPairGenerator.getInstance("Ed448", "BC");
+
             aes192kg = KeyGenerator.getInstance("AES", "BC");
             aes192kg.init(192, rand);
 
@@ -223,7 +228,17 @@
     {
         return dsaKpg.generateKeyPair();
     }
-    
+
+    public static KeyPair makeEd25519KeyPair()
+    {
+        return ed25519Kpg.generateKeyPair();
+    }
+
+    public static KeyPair makeEd448KeyPair()
+    {
+        return ed448Kpg.generateKeyPair();
+    }
+
     public static KeyPair makeEcDsaKeyPair()
     {
         return ecDsaKpg.generateKeyPair();
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java
index 9f4f591..6c20adc 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewAuthenticatedDataTest.java
@@ -25,11 +25,9 @@
 import org.bouncycastle.asn1.cms.CCMParameters;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.ContentInfo;
-import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
-import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cms.CMSAlgorithm;
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewEnvelopedDataTest.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewEnvelopedDataTest.java
index 1aa0993..fca6d08 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewEnvelopedDataTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewEnvelopedDataTest.java
@@ -1768,11 +1768,13 @@
             _origEcKP.getPrivate(), _origEcKP.getPublic(),
             wrapAlgorithm).addRecipient(_reciEcCert).setProvider(BC));
 
+        // DESEDE wrap should only be used with DES/TripleDES keys.
+        ASN1ObjectIdentifier encAlg = wrapAlgorithm.equals(CMSAlgorithm.DES_EDE3_WRAP) ? CMSAlgorithm.DES_EDE3_CBC : CMSAlgorithm.AES128_CBC;
         CMSEnvelopedData ed = edGen.generate(
             new CMSProcessableByteArray(data),
-            new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build());
+            new JceCMSContentEncryptorBuilder(encAlg).setProvider(BC).build());
 
-        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+        assertEquals(ed.getEncryptionAlgOID(), encAlg.getId());
 
         RecipientInformationStore recipients = ed.getRecipientInfos();
 
@@ -1789,7 +1791,7 @@
             assertNotNull(keyWrapAlg.getParameters());
         }
 
-        assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC);
+        assertEquals(ed.getEncryptionAlgOID(), encAlg.getId());
 
         confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC);
         confirmNumberRecipients(recipients, 1);
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataStreamTest.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataStreamTest.java
index fcd1266..b11de46 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataStreamTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataStreamTest.java
@@ -122,6 +122,9 @@
     private static X509CRL         _signCrl;
     private static X509CRL         _origCrl;
 
+    private static KeyPair         _signEd448KP;
+    private static X509Certificate _signEd448Cert;
+
     private static boolean         _initialised = false;
 
     public NewSignedDataStreamTest(String name)
@@ -164,6 +167,9 @@
 
             _signCrl  = CMSTestUtil.makeCrl(_signKP);
             _origCrl  = CMSTestUtil.makeCrl(_origKP);
+
+            _signEd448KP   = CMSTestUtil.makeEd448KeyPair();
+            _signEd448Cert = CMSTestUtil.makeCertificate(_signEd448KP, _signDN, _origKP, _origDN);
         }
     }
     
@@ -775,6 +781,74 @@
         verifyEncodedData(bOut);
     }
 
+    public void testEd448Encapsulated()
+        throws Exception
+    {
+        encapsulatedTest(_signEd448KP, _signEd448Cert, "Ed448", false);
+    }
+
+    private void encapsulatedTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm,
+        boolean isDirect)
+        throws Exception
+    {
+        List certList = new ArrayList();
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        certList.add(signatureCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();
+
+        ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(
+            new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder()
+                    .setProvider(BC)
+                    .build())
+                .setDirectSignature(isDirect).build(signer, signatureCert));
+
+        gen.addCertificates(certs);
+
+        OutputStream sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedDataParser sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build(), bOut.toByteArray());
+
+        sp.getSignedContent().drain();
+
+        verifySignatures(sp);
+
+        //
+        // try using existing signer
+        //
+        gen = new CMSSignedDataStreamGenerator();
+
+        gen.addSigners(sp.getSignerInfos());
+
+        gen.addCertificates(sp.getCertificates());
+
+        bOut.reset();
+
+        sigOut = gen.open(bOut, true);
+
+        sigOut.write(TEST_MESSAGE.getBytes());
+
+        sigOut.close();
+
+        CMSSignedData sd = new CMSSignedData(new CMSProcessableByteArray(TEST_MESSAGE.getBytes()), bOut.toByteArray());
+
+        assertEquals(1, sd.getSignerInfos().getSigners().size());
+
+        verifyEncodedData(bOut);
+    }
+
     public void testSHA1WithRSAEncapsulatedSubjectKeyID()
         throws Exception
     {
diff --git a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataTest.java b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataTest.java
index b354c52..efcdc3d 100644
--- a/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/cms/test/NewSignedDataTest.java
@@ -28,6 +28,7 @@
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
@@ -41,6 +42,7 @@
 import org.bouncycastle.asn1.cms.ContentInfo;
 import org.bouncycastle.asn1.cms.SignedData;
 import org.bouncycastle.asn1.cms.SignerInfo;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.ocsp.OCSPResponse;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -123,6 +125,12 @@
     private static KeyPair         _signDsaKP;
     private static X509Certificate _signDsaCert;
 
+    private static KeyPair         _signEd25519KP;
+    private static X509Certificate _signEd25519Cert;
+
+    private static KeyPair         _signEd448KP;
+    private static X509Certificate _signEd448Cert;
+
     private static String          _reciDN;
     private static KeyPair         _reciKP;
     private static X509Certificate _reciCert;
@@ -567,7 +575,7 @@
             + "HxxN0ZjpoIEiPa+NIth1W+W3Z7yt4JUM/zX0iyhza/bPHbPOLrsXZAhGy/qn"
             + "/JNmKBiVxuxZYYHI20CZHrgjb+ARczWuOJuBVEGEgbW/t7hMVX8X+MPAzZek"
             + "Ndi9ZfkurEeYdDpGluYBH910/P95ibG8nrJyTaoxhmOJhJ/o/SO54m8oDlI0");
-
+                                                     List crlList = new ArrayList();
     private static final Set noParams = new HashSet();
 
     static
@@ -590,6 +598,8 @@
         noParams.add(NISTObjectIdentifiers.id_ecdsa_with_sha3_256);
         noParams.add(NISTObjectIdentifiers.id_ecdsa_with_sha3_384);
         noParams.add(NISTObjectIdentifiers.id_ecdsa_with_sha3_512);
+        noParams.add(EdECObjectIdentifiers.id_Ed25519);
+        noParams.add(EdECObjectIdentifiers.id_Ed448);
     }
     
     public NewSignedDataTest(String name)
@@ -656,6 +666,12 @@
             _signEcGostKP = CMSTestUtil.makeEcGostKeyPair();
             _signEcGostCert = CMSTestUtil.makeCertificate(_signEcGostKP, _signDN, _origKP, _origDN);
 
+            _signEd25519KP   = CMSTestUtil.makeEd25519KeyPair();
+            _signEd25519Cert = CMSTestUtil.makeCertificate(_signEd25519KP, _signDN, _origKP, _origDN);
+
+            _signEd448KP   = CMSTestUtil.makeEd448KeyPair();
+            _signEd448Cert = CMSTestUtil.makeCertificate(_signEd448KP, _signDN, _origKP, _origDN);
+
             _reciDN   = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU";
             _reciKP   = CMSTestUtil.makeKeyPair();
             _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN);
@@ -1526,6 +1542,42 @@
         rsaPSSTest("SHA3-384withRSAandMGF1");
     }
 
+    public void testEd25519()
+        throws Exception
+    {
+        encapsulatedTest(_signEd25519KP, _signEd25519Cert, "Ed25519", EdECObjectIdentifiers.id_Ed25519, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512));
+    }
+
+    public void testEd448()
+        throws Exception
+    {
+        encapsulatedTest(_signEd448KP, _signEd448Cert, "Ed448", EdECObjectIdentifiers.id_Ed448, new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256_len, new ASN1Integer(512)));
+    }
+
+    public void testDetachedEd25519()
+        throws Exception
+    {
+        detachedTest(_signEd25519KP, _signEd25519Cert, "Ed25519", EdECObjectIdentifiers.id_Ed25519, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512));
+    }
+
+    public void testEdDetached448()
+        throws Exception
+    {
+        detachedTest(_signEd448KP, _signEd448Cert, "Ed448", EdECObjectIdentifiers.id_Ed448, new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256_len, new ASN1Integer(512)));
+    }
+
+    public void testEd25519WithNoAttr()
+        throws Exception
+    {
+        directSignatureTest(_signEd25519KP, _signEd25519Cert, "Ed25519", new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512));
+    }
+
+    public void testEd448WithNoAttr()
+        throws Exception
+    {
+        directSignatureTest(_signEd448KP, _signEd448Cert, "Ed448", new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256));
+    }
+
     public void testSHA3_224WithDSAEncapsulated()
         throws Exception
     {
@@ -1893,6 +1945,39 @@
         verifySignatures(s, md.digest("Hello world!".getBytes()));
     }
 
+    private void directSignatureTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm,
+        AlgorithmIdentifier digAlgId)
+    throws Exception
+    {
+        List              certList = new ArrayList();
+        CMSTypedData      msg = new CMSProcessableByteArray("Hello world!".getBytes());
+
+        certList.add(signatureCert);
+
+        Store           certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        JcaSignerInfoGeneratorBuilder siBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build());
+
+        siBuilder.setDirectSignature(true);
+
+        gen.addSignerInfoGenerator(siBuilder.build(contentSigner, signatureCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, false);
+     
+        assertTrue(s.getDigestAlgorithmIDs().contains(digAlgId));
+
+        verifySignatures(s, null);
+    }
+
     private void subjectKeyIDTest(
         KeyPair         signaturePair,
         X509Certificate signatureCert,
@@ -2008,6 +2093,17 @@
         ASN1ObjectIdentifier sigAlgOid)
         throws Exception
     {
+        encapsulatedTest(signaturePair, signatureCert, signatureAlgorithm, sigAlgOid, null);
+    }
+
+    private void encapsulatedTest(
+        KeyPair         signaturePair,
+        X509Certificate signatureCert,
+        String          signatureAlgorithm,
+        ASN1ObjectIdentifier sigAlgOid,
+        AlgorithmIdentifier digAlgId)
+    throws Exception
+    {
         List                certList = new ArrayList();
         List                crlList = new ArrayList();
         CMSTypedData        msg = new CMSProcessableByteArray("Hello World!".getBytes());
@@ -2026,20 +2122,24 @@
 
         gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, signatureCert));
 
-
         gen.addCertificates(certs);
     
         CMSSignedData s = gen.generate(msg, true);
-    
+
         ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
         ASN1InputStream      aIn = new ASN1InputStream(bIn);
-        
+
         s = new CMSSignedData(ContentInfo.getInstance(aIn.readObject()));
 
         Set digestAlgorithms = new HashSet(s.getDigestAlgorithmIDs());
 
         assertTrue(digestAlgorithms.size() > 0);
 
+        if (digAlgId != null)
+        {
+            assertTrue(digestAlgorithms.contains(digAlgId));
+        }
+
         certs = s.getCertificates();
     
         SignerInformationStore  signers = s.getSignerInfos();
@@ -2131,6 +2231,133 @@
         checkSignerStoreReplacement(s, signers);
     }
 
+    private void detachedTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm,
+        ASN1ObjectIdentifier sigAlgOid)
+        throws Exception
+    {
+        detachedTest(signaturePair, signatureCert, signatureAlgorithm, sigAlgOid, null);
+    }
+
+    private void detachedTest(
+        KeyPair signaturePair,
+        X509Certificate signatureCert,
+        String signatureAlgorithm,
+        ASN1ObjectIdentifier sigAlgOid,
+        AlgorithmIdentifier digAlgId)
+        throws Exception
+    {
+        List certList = new ArrayList();
+        CMSTypedData msg = new CMSProcessableByteArray("Hello World!".getBytes());
+
+        certList.add(signatureCert);
+
+        Store certs = new JcaCertStore(certList);
+
+        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
+
+        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC).build(signaturePair.getPrivate());
+
+        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(contentSigner, signatureCert));
+
+        gen.addCertificates(certs);
+
+        CMSSignedData s = gen.generate(msg, true);
+
+        ByteArrayInputStream bIn = new ByteArrayInputStream(s.getEncoded());
+        ASN1InputStream aIn = new ASN1InputStream(bIn);
+
+        s = new CMSSignedData(msg, ContentInfo.getInstance(aIn.readObject()));
+
+        Set digestAlgorithms = new HashSet(s.getDigestAlgorithmIDs());
+
+        assertTrue(digestAlgorithms.size() > 0);
+
+        if (digAlgId != null)
+        {
+            assertTrue(digestAlgorithms.contains(digAlgId));
+        }
+        certs = s.getCertificates();
+
+        SignerInformationStore signers = s.getSignerInfos();
+        Collection c = signers.getSigners();
+        Iterator it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            if (sigAlgOid != null)
+            {
+                assertEquals(sigAlgOid.getId(), signer.getEncryptionAlgOID());
+                if (noParams.contains(sigAlgOid))
+                {
+                    assertNull(signer.getEncryptionAlgParams());
+                }
+                else
+                {
+                    assertEquals(DERNull.INSTANCE, ASN1Primitive.fromByteArray(signer.getEncryptionAlgParams()));
+                }
+            }
+
+            digestAlgorithms.remove(signer.getDigestAlgorithmID());
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        assertTrue(digestAlgorithms.size() == 0);
+
+        //
+        // check signer information lookup
+        //
+
+        SignerId sid = new JcaSignerId(signatureCert);
+
+        Collection collection = signers.getSigners(sid);
+
+        assertEquals(1, collection.size());
+        assertTrue(collection.iterator().next() instanceof SignerInformation);
+
+        //
+        // try using existing signer
+        //
+
+        gen = new CMSSignedDataGenerator();
+
+        gen.addSigners(s.getSignerInfos());
+
+        gen.addCertificates(s.getCertificates());
+
+        s = gen.generate(msg);
+        
+        s = new CMSSignedData(msg, s.getEncoded());
+
+        certs = s.getCertificates();
+
+        signers = s.getSignerInfos();
+        c = signers.getSigners();
+        it = c.iterator();
+
+        while (it.hasNext())
+        {
+            SignerInformation signer = (SignerInformation)it.next();
+            Collection certCollection = certs.getMatches(signer.getSID());
+
+            Iterator certIt = certCollection.iterator();
+            X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
+
+            assertEquals(true, signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BC).build(cert)));
+        }
+
+        checkSignerStoreReplacement(s, signers);
+    }
+
     //
     // signerInformation store replacement test.
     //
diff --git a/bcpkix/src/main/java/org/bouncycastle/dvcs/test/DVCSParseTest.java b/bcpkix/src/main/java/org/bouncycastle/dvcs/test/DVCSParseTest.java
index 856d39d..36057f9 100644
--- a/bcpkix/src/main/java/org/bouncycastle/dvcs/test/DVCSParseTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/dvcs/test/DVCSParseTest.java
@@ -6,6 +6,7 @@
 import java.util.Iterator;
 import java.util.List;
 
+import junit.framework.TestCase;
 import org.bouncycastle.asn1.ASN1GeneralizedTime;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
@@ -37,8 +38,6 @@
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 
-import junit.framework.TestCase;
-
 public class DVCSParseTest
     extends TestCase
 {
diff --git a/bcpkix/src/main/java/org/bouncycastle/est/ESTResponse.java b/bcpkix/src/main/java/org/bouncycastle/est/ESTResponse.java
index efcc272..a4b2342 100644
--- a/bcpkix/src/main/java/org/bouncycastle/est/ESTResponse.java
+++ b/bcpkix/src/main/java/org/bouncycastle/est/ESTResponse.java
@@ -129,17 +129,14 @@
             };
         }
 
-        if (contentLength != null)
+        if (contentLength < 0)
         {
-            if (contentLength < 0)
-            {
-                throw new IOException("Server returned negative content length: " + absoluteReadLimit);
-            }
+            throw new IOException("Server returned negative content length: " + absoluteReadLimit);
+        }
 
-            if (absoluteReadLimit != null && contentLength >= absoluteReadLimit)
-            {
-                throw new IOException("Content length longer than absolute read limit: " + absoluteReadLimit + " Content-Length: " + contentLength);
-            }
+        if (absoluteReadLimit != null && contentLength >= absoluteReadLimit)
+        {
+            throw new IOException("Content length longer than absolute read limit: " + absoluteReadLimit + " Content-Length: " + contentLength);
         }
 
 
@@ -298,7 +295,6 @@
             throws IOException
         {
             int i = src.read();
-            System.out.print(String.valueOf((char)i));
             return i;
         }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/est/ESTService.java b/bcpkix/src/main/java/org/bouncycastle/est/ESTService.java
index 569f540..bd0da0f 100644
--- a/bcpkix/src/main/java/org/bouncycastle/est/ESTService.java
+++ b/bcpkix/src/main/java/org/bouncycastle/est/ESTService.java
@@ -119,7 +119,7 @@
      * @return A store of X509Certificates.
      */
     public CACertsResponse getCACerts()
-        throws Exception
+        throws ESTException
     {
         ESTResponse resp = null;
         Exception finalThrowable = null;
@@ -201,14 +201,12 @@
         {
             if (finalThrowable instanceof ESTException)
             {
-                throw finalThrowable;
+                throw (ESTException)finalThrowable;
             }
             throw new ESTException("Get CACerts: " + url.toString(), finalThrowable, resp.getStatusCode(), null);
         }
 
         return caCertsResponse;
-
-
     }
 
     /**
diff --git a/bcpkix/src/main/java/org/bouncycastle/est/jcajce/JsseESTServiceBuilder.java b/bcpkix/src/main/java/org/bouncycastle/est/jcajce/JsseESTServiceBuilder.java
index cdb5564..614e420 100644
--- a/bcpkix/src/main/java/org/bouncycastle/est/jcajce/JsseESTServiceBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/est/jcajce/JsseESTServiceBuilder.java
@@ -35,6 +35,22 @@
     /**
      * Create a builder for a client using a custom SSLSocketFactoryCreator.
      *
+     * @param hostName             hostName to talk to.
+     * @param socketFactoryCreator a custom creator of socket factories.
+     */
+    public JsseESTServiceBuilder(String hostName, int portNo, SSLSocketFactoryCreator socketFactoryCreator)
+    {
+        super(hostName + ":" + portNo);
+        if (socketFactoryCreator == null)
+        {
+            throw new NullPointerException("No socket factory creator.");
+        }
+        this.socketFactoryCreator = socketFactoryCreator;
+    }
+
+    /**
+     * Create a builder for a client using a custom SSLSocketFactoryCreator.
+     *
      * @param server               name of the server to talk to (URL format).
      * @param socketFactoryCreator a custom creator of socket factories.
      */
@@ -46,7 +62,6 @@
             throw new NullPointerException("No socket factory creator.");
         }
         this.socketFactoryCreator = socketFactoryCreator;
-
     }
 
     /**
@@ -63,8 +78,21 @@
     /**
      * Create a builder for a client talking to a trusted server.
      *
+     * @param hostName     name of the server to talk to.
+     * @param portNo       port number to connect on.
+     * @param trustManager trust manager to be used for validating the connection.
+     */
+    public JsseESTServiceBuilder(String hostName, int portNo, X509TrustManager trustManager)
+    {
+        super(hostName + ":" + portNo);
+        sslSocketFactoryCreatorBuilder = new SSLSocketFactoryCreatorBuilder(trustManager);
+    }
+
+    /**
+     * Create a builder for a client talking to a trusted server.
+     *
      * @param server       name of the server to talk to (URL format).
-     * @param trustManager
+     * @param trustManager trust manager to be used for validating the connection.
      */
     public JsseESTServiceBuilder(String server, X509TrustManager trustManager)
     {
@@ -75,13 +103,25 @@
     /**
      * Create a builder for a client talking to a trusted server.
      *
-     * @param server       name of the server to talk to (URL format).
-     * @param trustManager
+     * @param hostName      name of the server to talk to.
+     * @param portNo        port number to connect on.
+     * @param trustManagers trust managers that can be used for validating the connection.
      */
-    public JsseESTServiceBuilder(String server, X509TrustManager[] trustManager)
+    public JsseESTServiceBuilder(String hostName, int portNo, X509TrustManager[] trustManagers)
+    {
+        this(hostName + ":" + portNo, trustManagers);
+    }
+
+    /**
+     * Create a builder for a client talking to a trusted server.
+     *
+     * @param server       name of the server to talk to (URL format).
+     * @param trustManagers trust managers that can be used for validating the connection.
+     */
+    public JsseESTServiceBuilder(String server, X509TrustManager[] trustManagers)
     {
         super(server);
-        sslSocketFactoryCreatorBuilder = new SSLSocketFactoryCreatorBuilder(trustManager);
+        sslSocketFactoryCreatorBuilder = new SSLSocketFactoryCreatorBuilder(trustManagers);
     }
 
     public JsseESTServiceBuilder withHostNameAuthorizer(JsseHostnameAuthorizer hostNameAuthorizer)
diff --git a/bcpkix/src/main/java/org/bouncycastle/est/jcajce/SSLSocketFactoryCreatorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/est/jcajce/SSLSocketFactoryCreatorBuilder.java
index 7dbfd18..019af54 100644
--- a/bcpkix/src/main/java/org/bouncycastle/est/jcajce/SSLSocketFactoryCreatorBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/est/jcajce/SSLSocketFactoryCreatorBuilder.java
@@ -13,8 +13,6 @@
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.X509TrustManager;
 
-import org.bouncycastle.crypto.CryptoServicesRegistrar;
-
 /**
  * A basic builder to allow configuration of an SSLContext used to create an SSLSocketFactory.
  */
@@ -24,7 +22,7 @@
     protected Provider tlsProvider;
     protected KeyManager[] keyManagers;
     protected X509TrustManager[] trustManagers;
-    protected SecureRandom secureRandom = CryptoServicesRegistrar.getSecureRandom();
+    protected SecureRandom secureRandom;
 
     public SSLSocketFactoryCreatorBuilder(X509TrustManager trustManager)
     {
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64InputStream.java b/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64InputStream.java
index 75334ac..34144c6 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64InputStream.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64InputStream.java
@@ -96,87 +96,82 @@
     InputStream    in;
     int[]          outBuf = new int[3];
     int            bufPtr = 3;
-    boolean        isEndOfStream;
+//    boolean        isEndOfStream;
 
-    /**
-     * Create a stream for reading a PGP armoured message, parsing up to a header
-     * and then reading the data that follows.
-     *
-     * @param in
-     */
     public Base64InputStream(
         InputStream    in)
     {
         this.in = in;
     }
-    
+
     public int available()
         throws IOException
     {
-        return in.available();
+        // We can't guarantee 'in.available()' bytes aren't all spaces
+        return 0;
     }
-    
-    private int readIgnoreSpace() 
-        throws IOException
-    {
-        int    c = in.read();
-        
-        while (c == ' ' || c == '\t')
-        {
-            c = in.read();
-        }
-        
-        return c;
-    }
-    
+
     public int read()
         throws IOException
     {
-        int    c;
-
         if (bufPtr > 2)
         {
-            c = readIgnoreSpace();
-            
-            if (c == '\r' || c == '\n')
+            int in0 = readIgnoreSpaceFirst();
+            if (in0 < 0)
             {
-                c = readIgnoreSpace();
-                
-                while (c == '\n' || c == '\r')
-                {
-                    c = readIgnoreSpace();
-                }
-
-                if (c < 0)                // EOF
-                {
-                    isEndOfStream = true;
-                    return -1;
-                }
-
-                bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf);
+//                isEndOfStream = true;
+                return -1;
             }
-            else
-            {
-                if (c >= 0)
-                {
-                    bufPtr = decode(c, readIgnoreSpace(), readIgnoreSpace(), readIgnoreSpace(), outBuf);
-                }
-                else
-                {
-                    isEndOfStream = true;
-                    return -1;
-                }
-            }
+
+            int in1 = readIgnoreSpace();
+            int in2 = readIgnoreSpace();
+            int in3 = readIgnoreSpace();
+
+            bufPtr = decode(in0, in1, in2, in3, outBuf);
         }
 
-        c = outBuf[bufPtr++];
-
-        return c;
+        return outBuf[bufPtr++];
     }
-    
+
     public void close()
         throws IOException
     {
         in.close();
     }
+
+    private int readIgnoreSpace()
+        throws IOException
+    {
+        for (;;)
+        {
+            int c;
+            switch (c = in.read())
+            {
+            case ' ':
+            case '\t':
+                break;
+            default:
+                return c;
+            }
+        }
+    }
+
+    private int readIgnoreSpaceFirst()
+        throws IOException
+    {
+        for (;;)
+        {
+            int c;
+            switch (c = in.read())
+            {
+            case ' ':
+            case '\n':
+            case '\r':
+            case '\t':
+                break;
+            default:
+                return c;
+            }
+        }
+    }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64OutputStream.java b/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64OutputStream.java
index 51e7b1f..2b4e061 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64OutputStream.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/encoding/Base64OutputStream.java
@@ -4,32 +4,68 @@
 import java.io.IOException;
 import java.io.OutputStream;
 
-import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.encoders.Base64Encoder;
 
 public class Base64OutputStream
     extends FilterOutputStream
 {
-    byte[] buffer = new byte[54];
-    int    bufOff;
+    private static final Base64Encoder ENCODER = new Base64Encoder();
+    private static final int INBUF_SIZE = 54;
+    private static final int OUTBUF_SIZE = (((INBUF_SIZE + 2) / 3) * 4) + 2;
+
+    private final byte[] inBuf = new byte[INBUF_SIZE];
+    private final byte[] outBuf = new byte[OUTBUF_SIZE];
+
+    private int inPos = 0;
 
     public Base64OutputStream(OutputStream stream)
     {
         super(stream);
+
+        outBuf[OUTBUF_SIZE - 2] = (byte)'\r';
+        outBuf[OUTBUF_SIZE - 1] = (byte)'\n';
     }
 
     public void write(int b)
         throws IOException
     {
-        doWrite((byte)b);
+        inBuf[inPos++] = (byte)b;
+        if (inPos == INBUF_SIZE)
+        {
+            encodeBlock(inBuf, 0);
+            inPos = 0;
+        }
     }
 
-    public void write(byte[] buf, int bufOff, int len)
+    public void write(byte[] buf, int off, int len)
         throws IOException
     {
-        for (int i = 0; i != len; i++)
+        int available = INBUF_SIZE - inPos;
+        if (len < available)
         {
-            doWrite(buf[bufOff + i]);
+            System.arraycopy(buf, off, inBuf, inPos, len);
+            inPos += len;
+            return;
         }
+
+        int count = 0;
+        if (inPos > 0)
+        {
+            System.arraycopy(buf, off, inBuf, inPos, available);
+            count += available;
+            encodeBlock(inBuf, 0);
+//            inPos = 0;
+        }
+
+        int remaining;
+        while ((remaining = (len - count)) >= INBUF_SIZE)
+        {
+            encodeBlock(buf, off + count);
+            count += INBUF_SIZE;
+        }
+
+        System.arraycopy(buf, off + count, inBuf, 0, remaining);
+        inPos = remaining;
     }
 
     public void write(byte[] buf)
@@ -41,23 +77,24 @@
     public void close()
         throws IOException
     {
-        if (bufOff > 0)
+        if (inPos > 0)
         {
-            Base64.encode(buffer, 0, bufOff, out);
+            int outPos = ENCODER.encode(inBuf, 0, inPos, outBuf, 0);
+            inPos = 0;
+
+            outBuf[outPos++] = (byte)'\r';
+            outBuf[outPos++] = (byte)'\n';
+
+            out.write(outBuf, 0, outPos);
         }
+
         out.close();
     }
-    
-    private void doWrite(byte b)
+
+    private void encodeBlock(byte[] buf, int off)
         throws IOException
     {
-        buffer[bufOff++] = b;
-        if (bufOff == buffer.length)
-        {
-            Base64.encode(buffer, 0, buffer.length, out);
-            out.write('\r');
-            out.write('\n');
-            bufOff = 0;
-        }
+        ENCODER.encode(buf, off, INBUF_SIZE, outBuf, 0);
+        out.write(outBuf, 0, OUTBUF_SIZE);
     }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMEEnvelopedWriter.java b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMEEnvelopedWriter.java
index b8fdeee..9c22c38 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMEEnvelopedWriter.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMEEnvelopedWriter.java
@@ -119,7 +119,7 @@
 
         public SMIMEEnvelopedWriter build(OutputStream mimeOut, OutputEncryptor outEnc)
         {
-            return new SMIMEEnvelopedWriter(this, outEnc, mimeOut);
+            return new SMIMEEnvelopedWriter(this, outEnc, SMimeUtils.autoBuffer(mimeOut));
         }
     }
 
@@ -148,18 +148,16 @@
 
         try
         {
-            OutputStream outStream;
+            OutputStream backing = mimeOut;
 
             if ("base64".equals(contentTransferEncoding))
             {
-                outStream = new Base64OutputStream(mimeOut);
-                
-                return new ContentOutputStream(envGen.open(SMimeUtils.createUnclosable(outStream), outEnc), outStream);
+                backing = new Base64OutputStream(backing);
             }
-            else
-            {
-                return new ContentOutputStream(envGen.open(SMimeUtils.createUnclosable(mimeOut), outEnc), null);
-            }
+
+            OutputStream main = envGen.open(SMimeUtils.createUnclosable(backing), outEnc);
+
+            return new ContentOutputStream(main, backing);
         }
         catch (CMSException e)
         {
@@ -179,6 +177,18 @@
             this.backing = backing;
         }
 
+        public void write(byte[] buf)
+            throws IOException
+        {
+            main.write(buf);
+        }
+
+        public void write(byte[] buf, int off, int len)
+            throws IOException
+        {
+            main.write(buf, off, len);
+        }
+
         public void write(int i)
             throws IOException
         {
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMESignedWriter.java b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMESignedWriter.java
index 86e8361..8be6715 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMESignedWriter.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMIMESignedWriter.java
@@ -204,7 +204,7 @@
                 headers.put((String)ent.getKey(), (String)ent.getValue());
             }
 
-            return new SMIMESignedWriter(this, headers, boundary, mimeOut);
+            return new SMIMESignedWriter(this, headers, boundary, SMimeUtils.autoBuffer(mimeOut));
         }
 
         private void addHashHeader(
@@ -353,6 +353,18 @@
             this.sigBase = sigBase;
         }
 
+        public void write(byte[] buf)
+            throws IOException
+        {
+            main.write(buf);
+        }
+
+        public void write(byte[] buf, int off, int len)
+            throws IOException
+        {
+            main.write(buf, off, len);
+        }
+
         public void write(int i)
             throws IOException
         {
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeParserProvider.java b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeParserProvider.java
index 5c5bba6..7b3f897 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeParserProvider.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeParserProvider.java
@@ -24,12 +24,14 @@
     public MimeParser createParser(InputStream source)
         throws IOException
     {
-        return new BasicMimeParser(new SMimeParserContext(defaultContentTransferEncoding, digestCalculatorProvider), source);
+        return new BasicMimeParser(new SMimeParserContext(defaultContentTransferEncoding, digestCalculatorProvider),
+            SMimeUtils.autoBuffer(source));
     }
 
     public MimeParser createParser(Headers headers, InputStream source)
         throws IOException
     {
-        return new BasicMimeParser(new SMimeParserContext(defaultContentTransferEncoding, digestCalculatorProvider), headers, source);
+        return new BasicMimeParser(new SMimeParserContext(defaultContentTransferEncoding, digestCalculatorProvider),
+            headers, SMimeUtils.autoBuffer(source));
     }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeUtils.java b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeUtils.java
index 6f9daeb..86a4b8b 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeUtils.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/smime/SMimeUtils.java
@@ -1,7 +1,10 @@
 package org.bouncycastle.mime.smime;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Collections;
 import java.util.HashMap;
@@ -121,10 +124,44 @@
         return oid;
     }
 
+    static InputStream autoBuffer(InputStream input)
+    {
+        if (input instanceof java.io.FileInputStream)
+        {
+            return new BufferedInputStream(input);
+        }
+
+        return input;
+    }
+
+    static OutputStream autoBuffer(OutputStream output)
+    {
+        if (output instanceof java.io.FileOutputStream)
+        {
+            return new BufferedOutputStream(output);
+        }
+
+        return output;
+    }
+
     static OutputStream createUnclosable(OutputStream destination)
     {
         return new FilterOutputStream(destination)
         {
+            public void write(byte buf[], int off, int len)
+                throws IOException
+            {
+                if (buf == null)
+                {
+                    throw new NullPointerException();
+                }
+                if ((off | len | (buf.length - (len + off)) | (off + len)) < 0)
+                {
+                    throw new IndexOutOfBoundsException();
+                }
+                out.write(buf, off, len);
+            }
+
             public void close()
                 throws IOException
             {
diff --git a/bcpkix/src/main/java/org/bouncycastle/mime/test/Base64TransferEncodingTest.java b/bcpkix/src/main/java/org/bouncycastle/mime/test/Base64TransferEncodingTest.java
index cf765e2..19e65f8 100644
--- a/bcpkix/src/main/java/org/bouncycastle/mime/test/Base64TransferEncodingTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/mime/test/Base64TransferEncodingTest.java
@@ -1,10 +1,14 @@
 package org.bouncycastle.mime.test;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.security.SecureRandom;
 
 import junit.framework.TestCase;
+import org.bouncycastle.mime.encoding.Base64InputStream;
+import org.bouncycastle.mime.encoding.Base64OutputStream;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Base64;
 
 public class Base64TransferEncodingTest
@@ -144,26 +148,60 @@
         verifyDecode(new byte[0][0], new ByteArrayOutputStream());
     }
 
+    public void testEncode()
+        throws Exception
+    {
+        String[] b64Data = new String[] {
+            "a1D4qBB8l3wR/r8Y6bXDJfSlMkTWzPUZzQ==",
+            "MC47oxxB77T86ueJyw60uFbaCWBuwU7KHUg=",
+            "FuOBuA/bsPuFwfT5OeOYQhvZMJ+i3/cJxouDzJCekL2L2UTsXjpQ4V3IAx5rfAYhMl40OO63KzyFle11APcVPt41KyUmiC6cQKC0qMDVZnuiViZ18rTHZZAJ/CtidniQU2U7NDsLkJKO+8eCMhIhVBuZSyAIxJA2sixp/167gY6OqPeQnnsoFOJJO7t2H5/C6nXPXri0lX6I/RrKxjgsv8a9fQ292n01cOeC/bufYyJadcYXv1Mt4enlUZnOHIrUrsg2XG4CFv68vpSLUPngFAQTRNs6YVj6OcxOGFlWCCPlI0a51T6HqVehKF5O/InlQa8lOQcCGJtFYw==",
+            "GYS1vQkQqE3GTbgX+BDNc6d0/vFv4AbO9zcK9GjHdlB5gFo4UsKH5xtenAqzN6r5F4CYRrEZ1wkA16MtCxPttsccKuV8ssdvAA2QMdAVWK9k05xtEnqOaLqMdy9YuwsMyUwAtHuEeAO/oG/QRojDy1gfNit3F5vQNaF3IbOpgXBAbNHTp5UZrT3IfK9FD+ba2s/G82CaodSIkqRyMpzAKkt/A0nrbvUZCEv2JBN7LUUmJvfspVnaG/hwCCP+hbstA7cbjFJDje01zL4WMXC1ZC1Mpk+6D52mjQnpIaKOaEsfwW0hGJx5p71NfnrTp9JHl5690GpEO5NOnj8=",
+            "MWaSgP9N3JDSMsYK8R06y3n6TqeVdmSsJ4z5ukMmM7otJ59LAd957QFaeKRRXfDHDRlrP+FE6XI4JAvVh9+3VvtlGslVkjuBl0VNVLEwZOMO8sOpbZyPfepud8O0XD6/+pZUUz/zJFhK3OcFxoGFT0KurXGhncSzkJvs4bt3Omvxlcg2t5ucEOdKZE3AtzWrnFHV7iApF7cFc2R/cj/PYRu+N/60KhVKPt0eL9Kfm4Lr6ljMzpOTv0dtYjPUSp3QC4+uttrNLEEugx/ZMdhnmE+KEX/m/tkm5JLdTqZVqY4U4Dz8/fJgemBMhp4yn/G6RMMay4h36LU/hTFQiPQr5lwzDJSk2591I6PiBS3Nf1w1R1VRt5YrENnigdBpJScLiQJRLqWHbld6wgGGRIREkU3DX2VkB6y8q8N2HBk/ZmTy3tykc2WLk8lp6p3LlzmybK2v+bzgA3caQKp547OT/+oxnRZg0w4iGaHeMUhUULDtBsX3/O8Tc4d6shrI6hNO294+zxOq2XywUIIgjtwH7qUQmk17kRf2jvGxOvGRHsttdc8rW1RvYp0w3LKdAkabMHuOafRqjU4Ke0nY6MUUMXzyfAbMnhPlomHVcrf6t+B/K0tCvDt1zqchhqhhpEh8UnWBv///YwwaZYibD+Fsmu9Xhp+/hqp+KKu7zwDh+Zuf04RxADKQcGGFmtQmrJkyX4ewv84otPN5Eyfj2SiRw+8xkB5tbUE8CL0bxMEFGlm0TYwX9poqQyVo2MoNDFxBYjDUVw0wBPhCdmQjxB7iEoXuU+al3R1Abo/O1JbUXagoX3HhsfNGHAYqmgDiu2/ImYtuV4fYC9ExvAuJIlVIZllxU2ZjY2j0Z9mJJbGMMTMVw3T+2B+IBaSMGkZEpZVbN6Jxd0kG+aBtdM5LPQyI0mpdV16f4jl8AGAmMmZxYBhRNggvav+g6Iz6UZ3rzZL95OgCREjl3V0sozo6I/BkfGPtNd4+O+ZZ6Ovh0QqYaOLSupZLUuKB2rNn4T5Ziz73O63e6XYcAipR21FHq/Y7HKE7uYBGvOkCiK/I3ZKYvtZLwSTuTOcuHLBXS48ryxq5x3OjMK0/TlgbOKyQqUWqvioQ7tMXJQGjSR5MuNraFpfVMAHbymAFkofacKP/R6cdSSWe9Jo6HF9cL5gRKnjRFZiSw2gwLK9cZdSSEZZUE3Fkj6tCktYCklIDC+RsLZWRQLj0vtXSVUgmpTNGu+dSQRjZSx+IABmdPoEO4ftkG60HKLcvvYvgeCBcDRX5GhPFREkcspoqod0p7Tc4zN+/yGCZc487XJkiu/6QTNxlGItYMMEuTfxeJqbvHWFy83fG3gyC7ZkXd5pBPLR/+gybNzjyrVWusWe/JrH0mOvjpxmP6vkFXFM4XXN3BajYah03/pVeg/3NuEf98H0L7K17fsdXlS2qj4w7+CP2DHknIZ+GplFdg+GlTFVaBnErTeax5oGW/Pidd/hSwqSdihHVDsSSj1ZCaOZsxVcKupvz1KXlAGkiEfpvnuPg7ukCxq4Xue43+sb3aOyaKt17U7q6WDfX94+RAHIioUsvq/dNRghyMAwiV/Qssx/kn55ffqvDSFlPmQFhQYJ1R6VSDUEdHJi2sYXcXRM0MAlnpGk4s0IQYLbt9dmEaXhP+geadXcwkOIeEe8gwQeRvj2hUIB8o+kt9VzQKhT5+n46NtsbyVuf/MZXQQGXFAGhy8W4XryoFN8xRpgkYblEoXjv9dLrN+M/aWL++koMM5mJvm+lWKFsd4AwsAcSMZqsw/0kEvySJYZJphmGRDmnl5ECXJXG2B0jm1y4a08Ffurt5N8QmSPjVKwXRUfW+mCpRkDXZcMVJjPC+be1+TP1lXIVO97LuAUPImr0f9DDowuCNoJIXsSkA+FeBpclsWfMww17xjn2rmu9QQPbzaqCS4DKTw8OIzW59KE1pb/s4ss/kkFHOWODi5FGzzpZcSfTtdHt4hB+tjxO1PSu0/8DDG+nnw23+yWf+kKNm1ZfysJBat6Njgf/9i52TTEzA0Hplla4CeEC/lAj6bdOfpBKwtwH9Z2hAd9JLe5k1XAYOZWIgP5ADDbSmf7O2dX6/X7XHHPSwGXBbXnCdzvGGdyP3+FZ4kQ82tET/NQZp7yDdA0gFpn1dmN3R5GLX0uhCulIJA0KlBCSVNql15XpYabQYb+wmqMWEaWi0MmXNapIsYR1Ys1YYTAHzyykm217RJwgJoXTKRo5Bh/6WINxFlqztd3UeronhZNlngYDCaIVdGZ2W4VsnALcciSyh9yMJf6RYmHHiu5EQTuGbLzicDXXoqMyMBVakO6RJiTbZDYzq6yO5xq6Eed/a277IC6FSIZQO1obNqRsgaa/jpPy6aIRPdtk1OOyyaYsA7vNZ8Rc84HJPk9ntS8MVrdBa8qM/4DB+mG8/YvxiLCWa/rZ5zfd11S15v9V5WW1XRkDI84Z22290mSqqF1H0rteRZNhENzFDTxSw8VOJy0VAhP5bAqUGym0Nl/BsJrWs0Mr312rQ6d/4cbdhwcrDFTAEn4y9xmCXiuqYN/8v+3qUsjrPH03MGpgDdFR9Fdf+64f7gAIlGqXq6LWuDOS/m9rzXTqwsyp4wsLwPrELl1CLNRGfI1xzW/PoBgwsfxd7DkWdlsYxyEkdd6Tj7u7QQppFcxM4oaEwwcJqpqYgAVDuvHgheUwUI2yFBsMb5zzG6X8p/14l22rrE6akwj2dAVqtAPRbOwcvtKVu2QB0cibwTd7oVGTQ+qNVwt1YrawleTyk6PHvLkzjG2IjC5rOhvE9vdJwkqBv+twbMLdrj6AeiQaVsCPPssjVLscPaMP58umeeE/XwEXXZbrgB9HSn5Ur8AZeRfaMDAb6YK89tsa5i0CK//bqlSO1fSOZqQ0ZBNOwRtzQDtthdv5SPHU4HQ0v0odOTAqQoY5syhqtcB/cjsvny6GAsh+8I+pX7dvaxxDtHIgeCzBhqvShfx35eSkTV5TOZpxkt1+GWUC/EpUEcbV/i3NNS137+fL7QBp+a6Dh1C3VRu654pG1KEW+arH0JbzHOdMq2xYyO68bWFola7VXAPqedpwD3H+17/mK4jK1uYJcmvhSQbROGb8P9CQejUR4oAG0wvRpytTN24dBXD8/bkwgLOJ/90QRS8z1xFM9fR1/Nw+t1o3KHtGnadCsucYKHgWppKR9Y15zgnFbeMpFPl2DPBO95oiJAq5g6zM/QhFv3iyfdzix9v74EkXnMbcaWCOIZ9wg4XEzr3u2+Vde/Yq2lYF8aT8ItZ2AMPhED9+cjtnWceaHyo8TKLUHn+nUZmJXK0TSCU2t9gXjg==",
+            "jMAH2QhjoiYFVXOCffXkT+vhkXmsKabuwsau9kD0DbAcw+96Ia8kMXAQ81vLGpbhWulC9xS0XvR5duprtPabF+rG9si9g16lmuduYbKhwOQE1/84UKmKQ0T2XnxUsVkKtEKdRwARaWE/8UQ+2m6vU80jhpvGCdSNZBidFHy5mj+Zn5ppP6u8KJ7aLaJBJPwlnF0wYQltrLotf2++bi5KCUXuKJ6rTUffOpDBvhD2a2bBVC7hLyiNPpbhNu+FPPAlXBRTBfqYReyE7qDjKFUACJOKurS+BrT2W++Hj2bvuTI1516pCyUTifUmGT2z73+x95fJdHC64IM4yAnIAb9jDFcQ1dbirgeSpnkNIB9z4IifiveEj7KggdHf77/1qIHv83IqTknmxrVChhDK/QeNAWDkmVfbf42hDqFMMK3UsnxMU/0vkfjneB1JJEGWMQXiZU6xv0Adlj4jtfVrYQhkNyshFVu0e1ixT/EzcCt1HY4z4dpxC14r6fKniUg25EL3IM+DLShxZ2BknpXyKJrnhsr6Ob7Nfy4BU197YeLnFvSfJSRvB5JW2Fuv0nKEPkV4/1oSwy6mj3MeuBd0fXRczeFNfaxkZyJECD7sKreDzXm+uXR0rEKQaquRc2z71tryp2fdtsXNuP9MCVLrW5e3VF4B8VsMz2l+Rx54fNW78VsrdR5Xipz8V4VPeqmnI4pn2kjoOpa72yq6exBkLJkjrLoW/Yq0dNjqQco0ZP9vuysF2Tz7QADPNUUWoqm30XhBJzTRn5YPvtw3aEbJR9R4VrEWWzl0wHIjiext6eTzr8qbErlovE4w798J7oUxEYC01TeA4nMlM6Ut6J2XyBHfvuZy/bYzBUSO1uvZnDNyMCxAWYCBRnE6Wzn7UT49b/p9aW3eAn5g9FptJEXWA4ns6zkbyPGsJ80M9gfvSoyv2iWPpF0thPCaDeEtxa3Mjg/Sm7GGrpF+XXOoxFBzjcRhxKB1YzUGFTZ1M+JeT8HNGkmGEZQjorBdclf7wfXbZ5vGDuyMVQ9dDFmrCDZ+kwh/LS31vFq3BJ+o1ix8kTcG1xOywoG+vMNhSipFZlXyZNVSbtSNS6CvceCnkorJQUMtQAMaSwHvkhhIBLnMTI5YRR59mA0Axkh97jpkZCB8a19OBWcNSxtgVKjw0vPzme8k9BaPGR+x83K4qNU/OyCeLkrogacIExeyYucQaA2NsCyCtuber3z664E47VnqWJZ2wi+OzC16ZxpakCfyBcp4hiK8jy94pY0dZtm5JUTwCrGPWiL/d9d8JdWIUDBZx87NjLJ8IPROTyIIios2N8e8zl7UforK9D7JuaMKKmEXe4FapVo77DyliLBLhSzQNKfrsEdOlYc246qA3tB4c2T6PinsXOKGwldC1/dF0lVXcFlxHduTWatbBav33m0S2TArjYHPzL00zTqhLtedMSuMne3mEDLW9lsamv9MaU+iMFlHF5hsL1G6+TzpCu2yMix6yoBlQJj27sHLUNRrDimFAvPAqYcHrs14WL46URo3eBrv9o15S13Pm8sgT0W+1Ha72YVxrnMaNOtww6IySVrW069ejBp3BcE2xUyvZD5GbzDa/oKvD9KGLTPpD4KpAHKTve3i99dTZ5NIFaY/d1Kl2u9Uoj5kYO0NlX6jTeB2TvkM++3e8n33g3FBGhckIKzs/xAIxoiLn6DOK+bnDCqt2G6tc53AfO+hqlCY/e7TvflI6qdmj1avG0x8xEQHxQmqqRdRBMhWPg/EQeej9SVHNgqOe1AbFONJjcskoSI2NG4r/dBN4DExvrE1T7hENhxZfb9MYUa3j/1Oarjg9RG1pjEdQAZndrWUpQ3l8xzjo11yLqUxCc0O0QKmJAJjjXM7Ex/Ut3WCyxB4sqWrz9d0YvTDHwsEOBw2fQbbgetWItZBv+w9NVoStZNOQxJq6LsnsOoeQo9NgdwfDc445WlnInsg+4vvS35u9dXVTe8724O8Pvsbq+VLHVt8k18SwgZOH+ISplrfj5ctUnOuOv7MEf5eDeH+ybWux8PM1Bwo1OrKKgVftpkR0QMqlNBdnikGXksoar57lluYRk/miD6/en2jsePsu9NCcLw9GFSZiqpfL1PBVKSTh/7IrAoXzf+RcBakSftEBdZIW4/qQzs0RnGlGFF/Tylv26W/AAHFM9QQHdHCiTHGt5Au53xmRnAK3IVEaZRdeKUPkAdDygwplHjm0jGq9T/ZX6pe0JD+E6cG1GZJqJYJdV9aGIteBGhvzE4sk4VRk15Cv9zb/QXytAAQsqISbnI1YJ3gWCLM9t6VS8VTjDN1h8d/JFfKMul3eI6o+sbAyFPognunzww82RZX2PCS4METTBT4swD7bBCC/bOnTc4ZzbgezL3rtMRIwh1C8CvQi54PnhDE1Yr1HLdp9IYa5DYnNqfLoA6Ysg3KNYaRkqTu7Fx/YXs+rwSVCx7NGZHjRuY07KcuKhC0ro5MB+6se5DVp3CrnZ5Z2QNS5aTJ/Eto+Ulb/hvmL0T0DWL/RMm9kmPjWdJWqtl9DlidgmQy9vW1LPheTRFg0+FJDdbWGcORSACmFShgoKVTADHgLt1UL6JouMLfZKHQWFCQXD/hwrcuv7Sn5yD9g2sLgG/GZCbCPRF65S2t4w3fHOeu6/6ticjn0us6Ry6aiVE7lw/mC4Q06p/fh+KLNYJMJK/NKsvNAPK60Pf7TtFEnTtKVUCRd3OXaD/jFzx5KuPkntV3Xt47TrK1k6hj990FgrLRlZS5oG/qKUyfKxjDpLSdAsLn5KXevjufn8hltI4SpdUiLz/SY60nog3WJap3R6vwJe8kvg2qbIw0cgQfNUWpNArJYr45WM2LEW68R6u6xvJfbUZwnbLEHipvy1VZowXTfeFUt303Y9Dt/4c3DiF6pFvu5R1PK0cmVspZbZ/cKlb5zhlaIHwDwDzXztJAuqARxvbSzhTic1qxHAVeCGul/pJOyW1je3WT+Fa9Ey/vpnfZUHUlxCkygBJQFy1kpgx0B/vyJeCmkoHc0+BV8piolbKojS3+mvCV38e6317QCPZKJwL2QqJZqM3s+vfSqC8UTdbYG4kYrXRW3QPQH0GapjKsHtmNQoGPuuia2yOpzagEB4amzblFb9wGaUuVAXu54695tOoAU1dUiYB67V9KNd5Sqm0OsUbV/f6ucBpLcvAIiIq9XtQjWFB5sYCUD73z99a1+nBW1WZTdn31ZlHkNOUmlhYvZCI6w8+jzvD3iFeEyxT0VkRyYca3iAkfPmX79fTslMXLoUn+DRyn1PJNV0SVQIPxL7V5VGMZ924wL28cj88QCoyQ/tVety/zJPFApko="};
+
+        String[] processed = new String[]{
+            "a1D4qBB8l3wR/r8Y6bXDJfSlMkTWzPUZzQ==\r\n",
+            "MC47oxxB77T86ueJyw60uFbaCWBuwU7KHUg=\r\n",
+            "FuOBuA/bsPuFwfT5OeOYQhvZMJ+i3/cJxouDzJCekL2L2UTsXjpQ4V3IAx5rfAYhMl40OO63\r\n" +
+            "KzyFle11APcVPt41KyUmiC6cQKC0qMDVZnuiViZ18rTHZZAJ/CtidniQU2U7NDsLkJKO+8eC\r\n" +
+            "MhIhVBuZSyAIxJA2sixp/167gY6OqPeQnnsoFOJJO7t2H5/C6nXPXri0lX6I/RrKxjgsv8a9\r\n" +
+            "fQ292n01cOeC/bufYyJadcYXv1Mt4enlUZnOHIrUrsg2XG4CFv68vpSLUPngFAQTRNs6YVj6\r\n" +
+            "OcxOGFlWCCPlI0a51T6HqVehKF5O/InlQa8lOQcCGJtFYw==\r\n"
+        };
+
+        for (int i = 0; i != b64Data.length; i++)
+        {
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            Base64OutputStream b64Strm = new Base64OutputStream(bOut);
+            b64Strm.write(Base64.decode(b64Data[i]));
+            b64Strm.close();
+
+            String recovered = Strings.fromByteArray(bOut.toByteArray());
+            if (i < processed.length)
+            {
+                assertEquals(processed[i], recovered);
+            }
+            assertEquals(b64Data[i], recovered.replaceAll("\r\n", ""));
+        }
+    }
 
     private void verifyDecode(byte[][] original, ByteArrayOutputStream bos)
         throws IOException
     {
-//        MimeParserInputStream source = new MimeParserInputStream(new ByteArrayInputStream(bos.toByteArray()), 1024);
-//        Base64TransferDecoder bte = new Base64TransferDecoder(source, 1024);
-//
-//        for (byte[] row : original)
-//        {
-//            for (byte expected : row)
-//            {
-//                TestCase.assertEquals(expected & 0xFF, bte.read());
-//            }
-//        }
-//
-//        TestCase.assertEquals(-1, bte.read());
+        Base64InputStream bte = new Base64InputStream(new ByteArrayInputStream(bos.toByteArray()));
 
+        for (int i = 0; i != original.length; i++)
+        {
+            byte[] row = original[i];
+            for (int j = 0; j != row.length; j++)
+            {
+                TestCase.assertEquals(row[j] & 0xFF, bte.read());
+            }
+        }
+
+        TestCase.assertEquals(-1, bte.read());
     }
 
-
     /**
      * This test causes the final line of base64 to not be a multiple of 64.
      *
diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java b/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java
index ed73228..f6deeab 100644
--- a/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java
+++ b/bcpkix/src/main/java/org/bouncycastle/openssl/MiscPEMGenerator.java
@@ -131,7 +131,9 @@
             }
             else
             {
-                throw new IOException("Cannot identify private key");
+                type = "PRIVATE KEY";
+
+                encoding = info.getEncoded();
             }
         }
         else if (o instanceof SubjectPublicKeyInfo)
diff --git a/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java
index b9fba0d..fff8d4e 100644
--- a/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/openssl/test/ParserTest.java
@@ -11,6 +11,7 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.math.BigInteger;
+import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.PrivateKey;
@@ -18,9 +19,12 @@
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Signature;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
@@ -31,6 +35,8 @@
 import org.bouncycastle.asn1.x9.ECNamedCurveTable;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
+import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openssl.CertificateTrustBlock;
 import org.bouncycastle.openssl.PEMDecryptorProvider;
@@ -45,6 +51,7 @@
 import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
 import org.bouncycastle.operator.InputDecryptorProvider;
 import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
@@ -326,7 +333,34 @@
 
         checkTrustedCert(trusted);
 
+        //
+        // EdDSAKey
+        //
+        byte[] msg = Strings.toByteArray("Hello, world!");
 
+        pemRd = openPEMResource("eddsapriv.pem");
+
+        PrivateKeyInfo edPrivInfo = (PrivateKeyInfo)pemRd.readObject();
+
+        EdDSAPrivateKey edPrivKey = (EdDSAPrivateKey)new JcaPEMKeyConverter().setProvider("BC").getPrivateKey(edPrivInfo);
+
+        EdDSAPublicKey edPubKey = edPrivKey.getPublicKey();
+
+        Signature edSig = Signature.getInstance(edPrivKey.getAlgorithm(), "BC");
+
+        edSig.initSign(edPrivKey);
+
+        edSig.update(msg);
+
+        byte[] s = edSig.sign();
+
+        edSig.initVerify(edPubKey);
+
+        edSig.update(msg);
+
+        isTrue(edSig.verify(s));
+
+        doOpenSslGost2012Test();
     }
 
     private void checkTrustedCert(X509TrustedCertificateBlock trusted)
@@ -342,7 +376,7 @@
         {
             fail("key purpose usages wrong size");
         }
-        if (!trustBlock.getUses().contains(KeyPurposeId.id_kp_OCSPSigning))
+        if (!trustBlock.getUses().contains(KeyPurposeId.id_kp_OCSPSigning.toOID()))
         {
             fail("key purpose use not found");
         }
@@ -351,7 +385,7 @@
         {
             fail("key purpose prohibitions wrong size");
         }
-        if (!trustBlock.getProhibitions().contains(KeyPurposeId.id_kp_clientAuth))
+        if (!trustBlock.getProhibitions().contains(KeyPurposeId.id_kp_clientAuth.toOID()))
         {
             fail("key purpose prohibition not found");
         }
@@ -458,6 +492,36 @@
         keyDecryptTest(fileName, expectedPrivKeyClass, new BcPEMDecryptorProvider("changeit".toCharArray()));
     }
 
+    private void doOpenSslGost2012Test()
+        throws Exception
+    {
+        try
+        {
+            KeyFactory.getInstance("ECGOST3410-2012", "BC"); // check for algorithm
+        }
+        catch (Exception e)
+        {
+            return;
+        }
+
+        String fileName = "gost2012_priv.pem";
+
+        PEMParser pr = openPEMResource("data/" + fileName);
+        PKCS8EncryptedPrivateKeyInfo pInfo = (PKCS8EncryptedPrivateKeyInfo)pr.readObject();
+
+        InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC").build("test".toCharArray());
+
+        KeyFactory keyFact = KeyFactory.getInstance("ECGOST3410-2012", "BC");
+
+        PrivateKey privKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(pInfo.decryptPrivateKeyInfo(pkcs8Prov).getEncoded()));
+
+        pr = openPEMResource("data/gost2012_cert.pem");
+        X509Certificate cert = (X509Certificate)CertificateFactory.getInstance("X.509", "BC").generateCertificate(
+            new ByteArrayInputStream(((X509CertificateHolder)pr.readObject()).getEncoded()));
+
+        cert.verify(cert.getPublicKey());
+    }
+
     private void keyDecryptTest(String fileName, Class expectedPrivKeyClass, PEMDecryptorProvider decProv)
         throws IOException
     {
@@ -505,7 +569,7 @@
         catch (IOException e)
         {
             if (e.getCause() != null && !e.getCause().getMessage().endsWith(message))
-            {              System.err.println(e.getCause().getMessage());
+            {
                fail("issue " + index + " exception thrown, but wrong message");
             }
             else if (e.getCause() == null && !e.getMessage().equals(message))
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultAlgorithmNameFinder.java b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultAlgorithmNameFinder.java
index ad6905c..3ecade5 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultAlgorithmNameFinder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultAlgorithmNameFinder.java
@@ -51,6 +51,7 @@
         algorithms.put(NISTObjectIdentifiers.id_sha3_256, "SHA3-256");
         algorithms.put(NISTObjectIdentifiers.id_sha3_384, "SHA3-384");
         algorithms.put(NISTObjectIdentifiers.id_sha3_512, "SHA3-512");
+        algorithms.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
         algorithms.put(OIWObjectIdentifiers.elGamalAlgorithm, "ELGAMAL");
         algorithms.put(OIWObjectIdentifiers.idSHA1, "SHA1");
         algorithms.put(OIWObjectIdentifiers.md5WithRSA, "MD5WITHRSA");
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java
index fb6bdc1..d8ea1cb 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultDigestAlgorithmIdentifierFinder.java
@@ -3,12 +3,14 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
 import org.bouncycastle.asn1.bsi.BSIObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
@@ -30,6 +32,7 @@
         //
         // digests
         //
+        digestOids.put(OIWObjectIdentifiers.dsaWithSHA1, OIWObjectIdentifiers.idSHA1);
         digestOids.put(OIWObjectIdentifiers.md4WithRSAEncryption, PKCSObjectIdentifiers.md4);
         digestOids.put(OIWObjectIdentifiers.md4WithRSA, PKCSObjectIdentifiers.md4);
         digestOids.put(OIWObjectIdentifiers.sha1WithRSA, OIWObjectIdentifiers.idSHA1);
@@ -148,6 +151,14 @@
         {
             digAlgId = RSASSAPSSparams.getInstance(sigAlgId.getParameters()).getHashAlgorithm();
         }
+        else if (sigAlgId.getAlgorithm().equals(EdECObjectIdentifiers.id_Ed25519))
+        {
+            digAlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512);
+        }
+        else if (sigAlgId.getAlgorithm().equals(EdECObjectIdentifiers.id_Ed448))
+        {
+            digAlgId = new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256_len, new ASN1Integer(512));
+        }
         else
         {
             digAlgId = new AlgorithmIdentifier((ASN1ObjectIdentifier)digestOids.get(sigAlgId.getAlgorithm()), DERNull.INSTANCE);
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java
index 5aa7707..40a9ea0 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/DefaultSignatureAlgorithmIdentifierFinder.java
@@ -15,6 +15,7 @@
 import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
 import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -160,11 +161,15 @@
         algorithms.put("SHAKE128WITHXMSSMT-SHAKE128", BCObjectIdentifiers.xmss_mt_SHAKE128ph);
         algorithms.put("SHAKE256WITHXMSSMT-SHAKE256", BCObjectIdentifiers.xmss_mt_SHAKE256ph);
 
+        algorithms.put("LMS", PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
+
+        algorithms.put("XMSS", IsaraObjectIdentifiers.id_alg_xmss);
         algorithms.put("XMSS-SHA256", BCObjectIdentifiers.xmss_SHA256);
         algorithms.put("XMSS-SHA512", BCObjectIdentifiers.xmss_SHA512);
         algorithms.put("XMSS-SHAKE128", BCObjectIdentifiers.xmss_SHAKE128);
         algorithms.put("XMSS-SHAKE256", BCObjectIdentifiers.xmss_SHAKE256);
 
+        algorithms.put("XMSSMT", IsaraObjectIdentifiers.id_alg_xmssmt);
         algorithms.put("XMSSMT-SHA256", BCObjectIdentifiers.xmss_mt_SHA256);
         algorithms.put("XMSSMT-SHA512", BCObjectIdentifiers.xmss_mt_SHA512);
         algorithms.put("XMSSMT-SHAKE128", BCObjectIdentifiers.xmss_mt_SHAKE128);
@@ -183,6 +188,7 @@
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA384);
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA512);
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
+        noParams.add(OIWObjectIdentifiers.dsaWithSHA1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
@@ -231,6 +237,9 @@
         noParams.add(BCObjectIdentifiers.xmss_mt_SHAKE128);
         noParams.add(BCObjectIdentifiers.xmss_mt_SHAKE256);
 
+        noParams.add(IsaraObjectIdentifiers.id_alg_xmss);
+        noParams.add(IsaraObjectIdentifiers.id_alg_xmssmt);
+
         //
         // qTESLA
         //
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java b/bcpkix/src/main/java/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java
index 4d029dd..b01b370 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/bc/BcDigestCalculatorProvider.java
@@ -2,11 +2,9 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.Map;
 
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.Digest;
-import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.operator.DigestCalculator;
 import org.bouncycastle.operator.DigestCalculatorProvider;
 import org.bouncycastle.operator.OperatorCreationException;
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
index b834458..f219b11 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentSignerBuilder.java
@@ -1,5 +1,6 @@
 package org.bouncycastle.operator.jcajce;
 
+import java.io.IOException;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.PrivateKey;
@@ -10,12 +11,21 @@
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.security.spec.PSSParameterSpec;
+import java.util.List;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.CompositePrivateKey;
 import org.bouncycastle.jcajce.io.OutputStreamFactory;
+import org.bouncycastle.jcajce.spec.CompositeAlgorithmSpec;
 import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
 import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
 import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
@@ -25,6 +35,8 @@
 import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.RuntimeOperatorException;
+import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 public class JcaContentSignerBuilder
 {
@@ -53,6 +65,14 @@
             this.sigAlgId = new AlgorithmIdentifier(
                                     PKCSObjectIdentifiers.id_RSASSA_PSS, createPSSParams(pssSpec));
         }
+        else if (sigParamSpec instanceof CompositeAlgorithmSpec)
+        {
+            CompositeAlgorithmSpec compSpec = (CompositeAlgorithmSpec)sigParamSpec;
+
+            this.sigAlgSpec = compSpec;
+            this.sigAlgId = new AlgorithmIdentifier(
+                                    MiscObjectIdentifiers.id_alg_composite, createCompParams(compSpec));
+        }
         else
         {
             throw new IllegalArgumentException("unknown sigParamSpec: "
@@ -84,6 +104,11 @@
     public ContentSigner build(PrivateKey privateKey)
         throws OperatorCreationException
     {
+        if (privateKey instanceof CompositePrivateKey)
+        {
+            return buildComposite((CompositePrivateKey)privateKey);
+        }
+        
         try
         {
             final Signature sig = helper.createSignature(sigAlgId);
@@ -131,6 +156,81 @@
         }
     }
 
+    private ContentSigner buildComposite(CompositePrivateKey privateKey)
+        throws OperatorCreationException
+    {
+        try
+        {
+            List<PrivateKey> privateKeys = privateKey.getPrivateKeys();
+            final ASN1Sequence sigAlgIds = ASN1Sequence.getInstance(sigAlgId.getParameters());
+            final Signature[] sigs = new Signature[sigAlgIds.size()];
+
+            for (int i = 0; i != sigAlgIds.size(); i++)
+            {
+                sigs[i] = helper.createSignature(AlgorithmIdentifier.getInstance(sigAlgIds.getObjectAt(i)));
+
+                if (random != null)
+                {
+                    sigs[i].initSign(privateKeys.get(i), random);
+                }
+                else
+                {
+                    sigs[i].initSign(privateKeys.get(i));
+                }
+            }
+
+            OutputStream sStream = OutputStreamFactory.createStream(sigs[0]);
+            for (int i = 1; i != sigs.length; i++)
+            {
+                sStream = new TeeOutputStream(sStream, OutputStreamFactory.createStream(sigs[i]));
+            }
+
+            final OutputStream sigStream = sStream;
+
+            return new ContentSigner()
+            {
+                OutputStream stream = sigStream;
+
+                public AlgorithmIdentifier getAlgorithmIdentifier()
+                {
+                    return sigAlgId;
+                }
+
+                public OutputStream getOutputStream()
+                {
+                    return stream;
+                }
+
+                public byte[] getSignature()
+                {
+                    try
+                    {
+                        ASN1EncodableVector sigV = new ASN1EncodableVector();
+
+                        for (int i = 0; i != sigs.length; i++)
+                        {
+                            sigV.add(new DERBitString(sigs[i].sign()));
+                        }
+
+                        return new DERSequence(sigV).getEncoded(ASN1Encoding.DER);
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeOperatorException("exception encoding signature: " + e.getMessage(), e);
+                    }
+                    catch (SignatureException e)
+                    {
+                        throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+                    }
+                }
+            };
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new OperatorCreationException("cannot create signer: " + e.getMessage(), e);
+        }
+    }
+
     private static RSASSAPSSparams createPSSParams(PSSParameterSpec pssSpec)
     {
         DigestAlgorithmIdentifierFinder digFinder = new DefaultDigestAlgorithmIdentifierFinder();
@@ -143,4 +243,32 @@
             new ASN1Integer(pssSpec.getSaltLength()),
             new ASN1Integer(pssSpec.getTrailerField()));
     }
+
+    private static ASN1Sequence createCompParams(CompositeAlgorithmSpec compSpec)
+    {
+        SignatureAlgorithmIdentifierFinder algFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        List<String> algorithmNames = compSpec.getAlgorithmNames();
+        List<AlgorithmParameterSpec> algorithmSpecs = compSpec.getParameterSpecs();
+
+        for (int i = 0; i != algorithmNames.size(); i++)
+        {
+            AlgorithmParameterSpec sigSpec = algorithmSpecs.get(i);
+            if (sigSpec == null)
+            {
+                v.add(algFinder.find(algorithmNames.get(i)));
+            }
+            else if (sigSpec instanceof PSSParameterSpec)
+            {
+                v.add(createPSSParams((PSSParameterSpec)sigSpec));
+            }
+            else
+            {
+                throw new IllegalArgumentException("unrecognized parameterSpec");
+            }
+        }
+
+        return new DERSequence(v);
+    }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
index 67150bc..8f2102b 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JcaContentVerifierProviderBuilder.java
@@ -9,11 +9,16 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.List;
 
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.jcajce.CompositePublicKey;
 import org.bouncycastle.jcajce.io.OutputStreamFactory;
 import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
 import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
@@ -23,6 +28,7 @@
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.RawContentVerifier;
 import org.bouncycastle.operator.RuntimeOperatorException;
+import org.bouncycastle.util.io.TeeOutputStream;
 
 public class JcaContentVerifierProviderBuilder
 {
@@ -81,27 +87,34 @@
             public ContentVerifier get(AlgorithmIdentifier algorithm)
                 throws OperatorCreationException
             {
-                Signature sig;
-                try
+                if (algorithm.getAlgorithm().equals(MiscObjectIdentifiers.id_alg_composite))
                 {
-                    sig = helper.createSignature(algorithm);
-
-                    sig.initVerify(certificate.getPublicKey());
-                }
-                catch (GeneralSecurityException e)
-                {
-                    throw new OperatorCreationException("exception on setup: " + e, e);
-                }
-
-                Signature rawSig = createRawSig(algorithm, certificate.getPublicKey());
-
-                if (rawSig != null)
-                {
-                    return new RawSigVerifier(algorithm, sig, rawSig);
+                    return createCompositeVerifier(algorithm, certificate.getPublicKey());
                 }
                 else
                 {
-                    return new SigVerifier(algorithm, sig);
+                    Signature sig;
+                    try
+                    {
+                        sig = helper.createSignature(algorithm);
+
+                        sig.initVerify(certificate.getPublicKey());
+                    }
+                    catch (GeneralSecurityException e)
+                    {
+                        throw new OperatorCreationException("exception on setup: " + e, e);
+                    }
+
+                    Signature rawSig = createRawSig(algorithm, certificate.getPublicKey());
+
+                    if (rawSig != null)
+                    {
+                        return new RawSigVerifier(algorithm, sig, rawSig);
+                    }
+                    else
+                    {
+                        return new SigVerifier(algorithm, sig);
+                    }
                 }
             }
         };
@@ -125,17 +138,54 @@
             public ContentVerifier get(AlgorithmIdentifier algorithm)
                 throws OperatorCreationException
             {
-                Signature sig = createSignature(algorithm, publicKey);
-
-                Signature rawSig = createRawSig(algorithm, publicKey);
-
-                if (rawSig != null)
+                if (algorithm.getAlgorithm().equals(MiscObjectIdentifiers.id_alg_composite))
                 {
-                    return new RawSigVerifier(algorithm, sig, rawSig);
+                    return createCompositeVerifier(algorithm, publicKey);
+                }
+
+                if (publicKey instanceof CompositePublicKey)
+                {
+                    List<PublicKey> keys = ((CompositePublicKey)publicKey).getPublicKeys();
+
+                    for (int i = 0; i != keys.size(); i++)
+                    {
+                        try
+                        {
+                            Signature sig = createSignature(algorithm, (PublicKey)keys.get(i));
+
+                            Signature rawSig = createRawSig(algorithm, (PublicKey)keys.get(i));
+
+                            if (rawSig != null)
+                            {
+                                return new RawSigVerifier(algorithm, sig, rawSig);
+                            }
+                            else
+                            {
+                                return new SigVerifier(algorithm, sig);
+                            }
+                        }
+                        catch (OperatorCreationException e)
+                        {
+                            // skip incorrect keys
+                        }
+                    }
+
+                    throw new OperatorCreationException("no matching algorithm found for key");
                 }
                 else
                 {
-                    return new SigVerifier(algorithm, sig);
+                    Signature sig = createSignature(algorithm, publicKey);
+
+                    Signature rawSig = createRawSig(algorithm, publicKey);
+
+                    if (rawSig != null)
+                    {
+                        return new RawSigVerifier(algorithm, sig, rawSig);
+                    }
+                    else
+                    {
+                        return new SigVerifier(algorithm, sig);
+                    }
                 }
             }
         };
@@ -147,6 +197,51 @@
         return this.build(helper.convertPublicKey(publicKey));
     }
 
+    private ContentVerifier createCompositeVerifier(AlgorithmIdentifier compAlgId, PublicKey publicKey)
+        throws OperatorCreationException
+    {
+        if (publicKey instanceof CompositePublicKey)
+        {
+            List<PublicKey> pubKeys = ((CompositePublicKey)publicKey).getPublicKeys();
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(compAlgId.getParameters());
+            Signature[] sigs = new Signature[keySeq.size()];
+            for (int i = 0; i != keySeq.size(); i++)
+            {
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                if (pubKeys.get(i) != null)
+                {
+                    sigs[i] = createSignature(sigAlg, (PublicKey)pubKeys.get(i));
+                }
+                else
+                {
+                    sigs[i] = null;
+                }
+            }
+
+            return new CompositeVerifier(sigs);
+        }
+        else
+        {
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(compAlgId.getParameters());
+            Signature[] sigs = new Signature[keySeq.size()];
+            for (int i = 0; i != keySeq.size(); i++)
+            {
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                try
+                {
+                    sigs[i] = createSignature(sigAlg, publicKey);
+                }
+                catch (Exception e)
+                {
+                    sigs[i] = null;
+                    // continue
+                }
+            }
+
+            return new CompositeVerifier(sigs);
+        }
+    }
+
     private Signature createSignature(AlgorithmIdentifier algorithm, PublicKey publicKey)
         throws OperatorCreationException
     {
@@ -286,4 +381,70 @@
             }
         }
     }
+
+    private class CompositeVerifier
+        implements ContentVerifier
+    {
+        private Signature[] sigs;
+        private OutputStream stream;
+
+        public CompositeVerifier(Signature[] sigs)
+            throws OperatorCreationException
+        {
+            this.sigs = sigs;
+
+            int start = 0;
+            while (start < sigs.length && sigs[start] == null)
+            {
+                start++;
+            }
+
+            if (start == sigs.length)
+            {
+                throw new OperatorCreationException("no matching signature found in composite");
+            }
+            this.stream = OutputStreamFactory.createStream(sigs[start]);
+            for (int i = start + 1; i != sigs.length; i++)
+            {
+                if (sigs[i] != null)
+                {
+                    this.stream = new TeeOutputStream(this.stream, OutputStreamFactory.createStream(sigs[i]));
+                }
+            }
+        }
+
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return new AlgorithmIdentifier(MiscObjectIdentifiers.id_alg_composite);
+        }
+
+        public OutputStream getOutputStream()
+        {
+            return stream;
+        }
+
+        public boolean verify(byte[] expected)
+        {
+            try
+            {
+                ASN1Sequence sigSeq = ASN1Sequence.getInstance(expected);
+                boolean failed = false;
+                for (int i = 0; i != sigSeq.size(); i++)
+                {
+                    if (sigs[i] != null)
+                    {
+                        if (!sigs[i].verify(DERBitString.getInstance(sigSeq.getObjectAt(i)).getBytes()))
+                        {
+                            failed = true;
+                        }
+                    }
+                }
+                return !failed;
+            }
+            catch (SignatureException e)
+            {
+                throw new RuntimeOperatorException("exception obtaining signature: " + e.getMessage(), e);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
index f026b78..65b1cdd 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/JceAsymmetricKeyWrapper.java
@@ -179,10 +179,8 @@
         {
             try
             {
-                if (random == null)
-                {
-                    random = CryptoServicesRegistrar.getSecureRandom();
-                }
+                random = CryptoServicesRegistrar.getSecureRandom(random);
+
                 KeyPairGenerator kpGen = helper.createKeyPairGenerator(getAlgorithmIdentifier().getAlgorithm());
 
                 kpGen.initialize(((ECPublicKey)publicKey).getParams(), random);
diff --git a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/OperatorHelper.java b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/OperatorHelper.java
index 72f412b..11d7ba0 100644
--- a/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/OperatorHelper.java
+++ b/bcpkix/src/main/java/org/bouncycastle/operator/jcajce/OperatorHelper.java
@@ -24,12 +24,15 @@
 import javax.crypto.KeyAgreement;
 
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.bsi.BSIObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
 import org.bouncycastle.asn1.kisa.KISAObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.ntt.NTTObjectIdentifiers;
@@ -62,6 +65,8 @@
         //
         // reverse mappings
         //
+        oids.put(EdECObjectIdentifiers.id_Ed25519, "Ed25519");
+        oids.put(EdECObjectIdentifiers.id_Ed448, "Ed448");
         oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
         oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
         oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
@@ -82,6 +87,8 @@
         oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256WITHCVC-ECDSA");
         oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384WITHCVC-ECDSA");
         oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512WITHCVC-ECDSA");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmss, "XMSS");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT");
 
         oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
         oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
@@ -341,7 +348,14 @@
 
         try
         {
-            dig = helper.createMessageDigest(MessageDigestUtils.getDigestName(digAlgId.getAlgorithm()));
+            if (digAlgId.getAlgorithm().equals(NISTObjectIdentifiers.id_shake256_len))
+            {
+                dig = helper.createMessageDigest("SHAKE256-" + ASN1Integer.getInstance(digAlgId.getParameters()).getValue());
+            }
+            else
+            {
+                dig = helper.createMessageDigest(MessageDigestUtils.getDigestName(digAlgId.getAlgorithm()));
+            }
         }
         catch (NoSuchAlgorithmException e)
         {
@@ -366,18 +380,26 @@
     Signature createSignature(AlgorithmIdentifier sigAlgId)
         throws GeneralSecurityException
     {
+        String sigName = getSignatureName(sigAlgId);
         Signature sig;
 
         try
         {
-            sig = helper.createSignature(getSignatureName(sigAlgId));
+            sig = helper.createSignature(sigName);
         }
         catch (NoSuchAlgorithmException e)
         {
             //
             // try an alternate
             //
-            if (oids.get(sigAlgId.getAlgorithm()) != null)
+            if (sigName.endsWith("WITHRSAANDMGF1"))
+            {
+                String signatureAlgorithm =
+                    sigName.substring(0, sigName.indexOf('W')) + "WITHRSASSA-PSS";
+
+                sig = helper.createSignature(signatureAlgorithm);
+            }
+            else if (oids.get(sigAlgId.getAlgorithm()) != null)
             {
                 String signatureAlgorithm = (String)oids.get(sigAlgId.getAlgorithm());
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/PKCS10CertificationRequest.java b/bcpkix/src/main/java/org/bouncycastle/pkcs/PKCS10CertificationRequest.java
index d32e206..85bcef6 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/PKCS10CertificationRequest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/PKCS10CertificationRequest.java
@@ -32,7 +32,14 @@
     {
         try
         {
-            return CertificationRequest.getInstance(ASN1Primitive.fromByteArray(encoding));
+            CertificationRequest rv = CertificationRequest.getInstance(ASN1Primitive.fromByteArray(encoding));
+
+            if (rv == null)
+            {
+                throw new PKCSIOException("empty data passed to constructor");
+            }
+
+            return rv;
         }
         catch (ClassCastException e)
         {
@@ -51,6 +58,10 @@
      */
     public PKCS10CertificationRequest(CertificationRequest certificationRequest)
     {
+         if (certificationRequest == null)
+         {
+             throw new NullPointerException("certificationRequest cannot be null");
+         }
          this.certificationRequest = certificationRequest;
     }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java b/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java
index 2edce23..d83c563 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/PKCS12PBEUtils.java
@@ -46,7 +46,7 @@
         noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC4);
         noIvAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC4);
 
-        desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC);
+        desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC);
         desAlgs.add(PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC);
     }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/package.html b/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/package.html
index bb47e1a..cb08559 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/bc/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 BC lightweight API extensions and operators for the PKCS#10 certification request package.
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java b/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java
index e4a7d65..da06626 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/JcePKCSPBEOutputEncryptorBuilder.java
@@ -9,6 +9,7 @@
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Primitive;
@@ -32,6 +33,8 @@
 import org.bouncycastle.jcajce.util.JcaJceHelper;
 import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
 import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
+import org.bouncycastle.operator.AlgorithmNameFinder;
+import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
 import org.bouncycastle.operator.DefaultSecretKeySizeProvider;
 import org.bouncycastle.operator.GenericKey;
 import org.bouncycastle.operator.OperatorCreationException;
@@ -47,6 +50,7 @@
     private ASN1ObjectIdentifier keyEncAlgorithm;
     private SecureRandom random;
     private SecretKeySizeProvider keySizeProvider = DefaultSecretKeySizeProvider.INSTANCE;
+    private AlgorithmNameFinder algorithmNameFinder = new DefaultAlgorithmNameFinder();
     private int iterationCount = 1024;
     private PBKDF2Config.Builder pbkdfBuilder = new PBKDF2Config.Builder();
 
@@ -203,7 +207,7 @@
 
                     cipher = helper.createCipher(keyEncAlgorithm.getId());
 
-                    cipher.init(Cipher.ENCRYPT_MODE, key, random);
+                    cipher.init(Cipher.ENCRYPT_MODE, simplifyPbeKey(key), random);
 
                     PBES2Parameters algParams = new PBES2Parameters(
                         new KeyDerivationFunc(MiscObjectIdentifiers.id_scrypt, params),
@@ -226,7 +230,7 @@
 
                     cipher = helper.createCipher(keyEncAlgorithm.getId());
 
-                    cipher.init(Cipher.ENCRYPT_MODE, key, random);
+                    cipher.init(Cipher.ENCRYPT_MODE, simplifyPbeKey(key), random);
 
                     AlgorithmParameters algP = cipher.getParameters();
 
@@ -284,6 +288,22 @@
         }
     }
 
+    // some providers struggle with generic algorithm names in keys.
+    private SecretKey simplifyPbeKey(SecretKey key)
+    {
+        if (algorithmNameFinder.hasAlgorithmName(keyEncAlgorithm))
+        {
+            String algName = algorithmNameFinder.getAlgorithmName(keyEncAlgorithm);
+
+            if (algName.indexOf("AES") >= 0)
+            {
+                key = new SecretKeySpec(key.getEncoded(), "AES");
+            }
+        }
+
+        return key;
+    }
+
     private boolean isPKCS12(ASN1ObjectIdentifier algorithm)
     {
         return algorithm.on(PKCSObjectIdentifiers.pkcs_12PbeIds)
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/package.html b/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/package.html
index 5305be4..d959084 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/jcajce/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 JCA extensions and operators for the PKCS#10 certification request package.
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/package.html b/bcpkix/src/main/java/org/bouncycastle/pkcs/package.html
index c83de7c..b93d1de 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/package.html
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/package.html
@@ -1,5 +1,5 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-        "http://www.w3.org/TR/html4/loose.dtd">
+        "https://www.w3.org/TR/html4/loose.dtd">
 <html>
 <body bgcolor="#ffffff">
 Basic support package for handling and creating PKCS#10 certification requests, PKCS#8 encrypted keys and PKCS#12 keys stores.
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/test/BCTestSetup.java b/bcpkix/src/main/java/org/bouncycastle/pkcs/test/BCTestSetup.java
index 7a13561..a45ba6f 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/test/BCTestSetup.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/test/BCTestSetup.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2005 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
+// Copyright (c) 2005 The Legion Of The Bouncy Castle (https://www.bouncycastle.org)
 package org.bouncycastle.pkcs.test;
 
 import java.security.Security;
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkcs/test/PfxPduTest.java b/bcpkix/src/main/java/org/bouncycastle/pkcs/test/PfxPduTest.java
index be1f769..d3c4d37 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkcs/test/PfxPduTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkcs/test/PfxPduTest.java
@@ -840,7 +840,13 @@
         KeyFactory fact = KeyFactory.getInstance("RSA", BC);
         PrivateKey privKey = fact.generatePrivate(privKeySpec);
         EncryptedPrivateKeyInfo encPKInfo = new EncryptedPrivateKeyInfo(encodedEncPKInfo);
+        AlgorithmParameters algParams = encPKInfo.getAlgParameters();
 
+        if (algParams == null)
+        {
+            return; // this PBE type is not supported on the JVM
+        }
+        
         Cipher cipher = Cipher.getInstance(encPKInfo.getAlgName(), "BC");
 
         PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
@@ -849,7 +855,6 @@
 
         Key pbeKey = skFac.generateSecret(pbeKeySpec);
 
-        AlgorithmParameters algParams = encPKInfo.getAlgParameters();
 
         cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/PKIXIdentity.java b/bcpkix/src/main/java/org/bouncycastle/pkix/PKIXIdentity.java
index 84ac046..e05049d 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/PKIXIdentity.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/PKIXIdentity.java
@@ -14,6 +14,13 @@
     private final PrivateKeyInfo privateKeyInfo;
     private final X509CertificateHolder[] certificateHolders;
 
+    /**
+     * Base constructor - a private key and its associated certificate chain. The chain
+     * should be ordered so that certificateHolders[0] is the matching public key for privKey.
+     *
+     * @param privateKeyInfo the private key.
+     * @param certificateHolders the public key certificates identifying it.
+     */
     public PKIXIdentity(PrivateKeyInfo privateKeyInfo, X509CertificateHolder[] certificateHolders)
     {
         this.privateKeyInfo = privateKeyInfo;
@@ -22,6 +29,17 @@
     }
 
     /**
+     * Base constructor - a private key and its associated public key certificate.
+     *
+     * @param privateKeyInfo the private key.
+     * @param certHolder privKey's matching public key certificate.
+     */
+    public PKIXIdentity(PrivateKeyInfo privateKeyInfo, X509CertificateHolder certHolder)
+    {
+        this(privateKeyInfo, new X509CertificateHolder[] { certHolder });
+    }
+
+    /**
      * Return the private key info for this identity.
      *
      * @return the identity's private key (if available, null otherwise).
@@ -42,6 +60,20 @@
     }
 
     /**
+     * Return the certificate chain associated with the private key info.
+     *
+     * @return the certificate chain.
+     */
+    public X509CertificateHolder[] getCertificateChain()
+    {
+        X509CertificateHolder[] rv = new X509CertificateHolder[certificateHolders.length];
+
+        System.arraycopy(certificateHolders, 0, rv, 0, rv.length);
+
+        return rv;
+    }
+
+    /**
      * Return a RecipientId for the identity's (private key, certificate) pair.
      */
     public RecipientId getRecipientId()
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/JcaPKIXIdentity.java b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/JcaPKIXIdentity.java
index 4872c1b..e5c8a9c 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/JcaPKIXIdentity.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/JcaPKIXIdentity.java
@@ -49,6 +49,13 @@
         }
     }
 
+    /**
+     * Base constructor - a private key and its associated certificate chain. The chain
+     * should be ordered so that certs[0] is the matching public key for privKey.
+     *
+     * @param privKey the private key.
+     * @param certs the public key certificates identifying it.
+     */
     public JcaPKIXIdentity(PrivateKey privKey, X509Certificate[] certs)
     {
         super(getPrivateKeyInfo(privKey), getCertificates(certs));
@@ -60,6 +67,17 @@
     }
 
     /**
+     * Base constructor - a private key and its associated public key certificate.
+     *
+     * @param privKey the private key.
+     * @param cert privKey's matching public key certificate.
+     */
+    public JcaPKIXIdentity(PrivateKey privKey, X509Certificate cert)
+    {
+        this(privKey, new X509Certificate[] { cert });
+    }
+
+    /**
      * Return the private key for this identity.
      *
      * @return the identity's private key.
@@ -78,4 +96,18 @@
     {
         return certs[0];
     }
+
+    /**
+     * Return the certificate chain associated with the private key.
+     *
+     * @return the certificate chain.
+     */
+    public X509Certificate[] getX509CertificateChain()
+    {
+        X509Certificate[] rv = new X509Certificate[certs.length];
+
+        System.arraycopy(certs, 0, rv, 0, rv.length);
+
+        return rv;
+    }
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCRLUtil.java b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCRLUtil.java
index 19e02ce..bf3b7de 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCRLUtil.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCRLUtil.java
@@ -4,7 +4,6 @@
 import java.security.cert.CertStoreException;
 import java.security.cert.X509CRL;
 import java.security.cert.X509Certificate;
-import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -15,18 +14,18 @@
 import org.bouncycastle.util.Store;
 import org.bouncycastle.util.StoreException;
 
-class PKIXCRLUtil
+abstract class PKIXCRLUtil
 {
-    public Set findCRLs(PKIXCRLStoreSelector crlselect, Date validityDate, List certStores, List pkixCrlStores)
+    static Set findCRLs(PKIXCRLStoreSelector crlselect, Date validityDate, List certStores, List pkixCrlStores)
         throws AnnotatedException
     {
-        Set initialSet = new HashSet();
+        HashSet initialSet = new HashSet();
 
         // get complete CRL(s)
         try
         {
-            initialSet.addAll(findCRLs(crlselect, pkixCrlStores));
-            initialSet.addAll(findCRLs(crlselect, certStores));
+            findCRLs(initialSet, crlselect, pkixCrlStores);
+            findCRLs(initialSet, crlselect, certStores);
         }
         catch (AnnotatedException e)
         {
@@ -44,14 +43,7 @@
             {
                 X509Certificate cert = crlselect.getCertificateChecking();
 
-                if (cert != null)
-                {
-                    if (crl.getThisUpdate().before(cert.getNotAfter()))
-                    {
-                        finalSet.add(crl);
-                    }
-                }
-                else
+                if (null == cert || crl.getThisUpdate().before(cert.getNotAfter()))
                 {
                     finalSet.add(crl);
                 }
@@ -62,35 +54,28 @@
     }
 
     /**
-     * Return a Collection of all CRLs found in the X509Store's that are
-     * matching the crlSelect criteriums.
+     * Add to a HashSet any and all CRLs found in the X509Store's that are matching the crlSelect
+     * criteria.
      *
-     * @param crlSelect a {@link PKIXCRLStoreSelector} object that will be used
-     *            to select the CRLs
-     * @param crlStores a List containing only
-     *            {@link Store} objects.
-     *            These are used to search for CRLs
-     *
-     * @return a Collection of all found {@link X509CRL X509CRL} objects. May be
-     *         empty but never <code>null</code>.
+     * @param crls
+     *            the {@link HashSet} to add the CRLs to.
+     * @param crlSelect
+     *            a {@link PKIXCRLStoreSelector} object that will be used to select the CRLs
+     * @param crlStores
+     *            a List containing only {@link Store} objects. These are used to search for CRLs
      */
-    private final Collection findCRLs(PKIXCRLStoreSelector crlSelect,
-        List crlStores) throws AnnotatedException
+    private static void findCRLs(HashSet crls, PKIXCRLStoreSelector crlSelect, List crlStores) throws AnnotatedException
     {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
-
         AnnotatedException lastException = null;
         boolean foundValidStore = false;
 
+        Iterator iter = crlStores.iterator();
         while (iter.hasNext())
         {
             Object obj = iter.next();
-
             if (obj instanceof Store)
             {
                 Store store = (Store)obj;
-
                 try
                 {
                     crls.addAll(store.getMatches(crlSelect));
@@ -98,14 +83,12 @@
                 }
                 catch (StoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
             else
             {
                 CertStore store = (CertStore)obj;
-
                 try
                 {
                     crls.addAll(PKIXCRLStoreSelector.getCRLs(crlSelect, store));
@@ -113,8 +96,7 @@
                 }
                 catch (CertStoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
         }
@@ -122,7 +104,5 @@
         {
             throw lastException;
         }
-        return crls;
     }
-
 }
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RFC3280CertPathUtilities.java b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RFC3280CertPathUtilities.java
index 42ef5e3..83b8ddc 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RFC3280CertPathUtilities.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RFC3280CertPathUtilities.java
@@ -11,11 +11,11 @@
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Extension;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -43,8 +43,6 @@
 
 class RFC3280CertPathUtilities
 {
-    private static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
-
     /**
      * If the complete CRL includes an issuing distribution point (IDP) CRL
      * extension check the following:
@@ -387,7 +385,7 @@
      * @param defaultCRLSignCert The issuer certificate of the certificate <code>cert</code>.
      * @param defaultCRLSignKey  The public key of the issuer certificate
      *                           <code>defaultCRLSignCert</code>.
-     * @param paramsPKIX         paramsPKIX PKIX parameters.
+     * @param paramsPKIX         PKIX parameters.
      * @param certPathCerts      The certificates on the certification path.
      * @return A <code>Set</code> with all keys of possible CRL issuer
      *         certificates.
@@ -422,11 +420,11 @@
         PKIXCertStoreSelector selector = new PKIXCertStoreSelector.Builder(certSelector).build();
 
         // get CRL signing certs
-        Collection coll;
+        LinkedHashSet coll = new LinkedHashSet();
         try
         {
-            coll = RevocationUtilities.findCertificates(selector, paramsPKIX.getCertificateStores());
-            coll.addAll(RevocationUtilities.findCertificates(selector, paramsPKIX.getCertStores()));
+            RevocationUtilities.findCertificates(coll, selector, paramsPKIX.getCertificateStores());
+            RevocationUtilities.findCertificates(coll, selector, paramsPKIX.getCertStores());
         }
         catch (AnnotatedException e)
         {
@@ -435,11 +433,10 @@
 
         coll.add(defaultCRLSignCert);
 
-        Iterator cert_it = coll.iterator();
-
         List validCerts = new ArrayList();
         List validKeys = new ArrayList();
 
+        Iterator cert_it = coll.iterator();
         while (cert_it.hasNext())
         {
             X509Certificate signingCert = (X509Certificate)cert_it.next();
@@ -507,9 +504,9 @@
         for (int i = 0; i < validCerts.size(); i++)
         {
             X509Certificate signCert = (X509Certificate)validCerts.get(i);
-            boolean[] keyusage = signCert.getKeyUsage();
+            boolean[] keyUsage = signCert.getKeyUsage();
 
-            if (keyusage != null && (keyusage.length < 7 || !keyusage[CRL_SIGN]))
+            if (keyUsage != null && (keyUsage.length <= CRL_SIGN || !keyUsage[CRL_SIGN]))
             {
                 lastException = new AnnotatedException(
                     "Issuer certificate key usage extension does not permit CRL signing.");
@@ -583,8 +580,8 @@
     }
 
     protected static Set processCRLA1i(
-        Date currentDate,
         PKIXExtendedParameters paramsPKIX,
+        Date currentDate,
         X509Certificate cert,
         X509CRL crl)
         throws AnnotatedException
@@ -645,13 +642,13 @@
     }
 
     protected static Set[] processCRLA1ii(
-        Date currentDate,
         PKIXExtendedParameters paramsPKIX,
+        Date currentDate,
+        Date validityDate,
         X509Certificate cert,
         X509CRL crl)
         throws AnnotatedException
     {
-        Set deltaSet = new HashSet();
         X509CRLSelector crlselect = new X509CRLSelector();
         crlselect.setCertificateChecking(cert);
 
@@ -666,14 +663,9 @@
 
         PKIXCRLStoreSelector extSelect = new PKIXCRLStoreSelector.Builder(crlselect).setCompleteCRLEnabled(true).build();
 
-        Date validityDate = currentDate;
-
-        if (paramsPKIX.getDate() != null)
-        {
-            validityDate = paramsPKIX.getDate();
-        }
-
-        Set completeSet = CRL_UTIL.findCRLs(extSelect, validityDate, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
+        Set completeSet = PKIXCRLUtil.findCRLs(extSelect, validityDate, paramsPKIX.getCertStores(),
+            paramsPKIX.getCRLStores());
+        Set deltaSet = new HashSet();
 
         if (paramsPKIX.isUseDeltasEnabled())
         {
@@ -687,14 +679,9 @@
                 throw new AnnotatedException("Exception obtaining delta CRLs.", e);
             }
         }
-        return new Set[]
-            {
-                completeSet,
-                deltaSet};
+        return new Set[]{ completeSet, deltaSet };
     }
 
-
-
     /**
      * If use-deltas is set, verify the issuer and scope of the delta CRL.
      *
@@ -857,8 +844,9 @@
     static void checkCRL(
         DistributionPoint dp,
         PKIXExtendedParameters paramsPKIX,
+        Date currentDate,
+        Date validityDate,
         X509Certificate cert,
-        Date validDate,
         X509Certificate defaultCRLSignCert,
         PublicKey defaultCRLSignKey,
         CertStatus certStatus,
@@ -867,8 +855,7 @@
         JcaJceHelper helper)
         throws AnnotatedException, CRLNotFoundException
     {
-        Date currentDate = new Date(System.currentTimeMillis());
-        if (validDate.getTime() > currentDate.getTime())
+        if (validityDate.getTime() > currentDate.getTime())
         {
             throw new AnnotatedException("Validation time is in future.");
         }
@@ -881,13 +868,6 @@
          * getAdditionalStore()
          */
 
-        Date validityDate = currentDate;
-
-        if (paramsPKIX.getDate() != null)
-        {
-            validityDate = paramsPKIX.getDate();
-        }
-
         Set crls = RevocationUtilities.getCompleteCRLs(dp, cert, validityDate, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
         boolean validCrlFound = false;
         AnnotatedException lastException = null;
@@ -965,10 +945,10 @@
                 RFC3280CertPathUtilities.processCRLC(deltaCRL, crl, paramsPKIX);
 
                 // (i)
-                RFC3280CertPathUtilities.processCRLI(validDate, deltaCRL, cert, certStatus, paramsPKIX);
+                RFC3280CertPathUtilities.processCRLI(validityDate, deltaCRL, cert, certStatus, paramsPKIX);
 
                 // (j)
-                RFC3280CertPathUtilities.processCRLJ(validDate, crl, cert, certStatus);
+                RFC3280CertPathUtilities.processCRLJ(validityDate, crl, cert, certStatus);
 
                 // (k)
                 if (certStatus.getCertStatus() == CRLReason.removeFromCRL)
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RevocationUtilities.java b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RevocationUtilities.java
index 5a53f26..1354861 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RevocationUtilities.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/RevocationUtilities.java
@@ -2,26 +2,20 @@
 
 import java.io.IOException;
 import java.math.BigInteger;
-import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.PublicKey;
 import java.security.cert.CRLException;
-import java.security.cert.CertPath;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
 import java.security.cert.CertStoreException;
 import java.security.cert.Certificate;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.TrustAnchor;
 import java.security.cert.X509CRL;
 import java.security.cert.X509CRLEntry;
 import java.security.cert.X509CRLSelector;
-import java.security.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
 import java.security.interfaces.DSAParams;
 import java.security.interfaces.DSAPublicKey;
 import java.security.spec.DSAPublicKeySpec;
-import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -36,18 +30,12 @@
 import javax.security.auth.x500.X500Principal;
 
 import org.bouncycastle.asn1.ASN1Enumerated;
-import org.bouncycastle.asn1.ASN1GeneralizedTime;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x500.style.RFC4519Style;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
 import org.bouncycastle.asn1.x509.CRLDistPoint;
 import org.bouncycastle.asn1.x509.CRLReason;
 import org.bouncycastle.asn1.x509.DistributionPoint;
@@ -56,10 +44,8 @@
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
-import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.jcajce.PKIXCRLStore;
 import org.bouncycastle.jcajce.PKIXCRLStoreSelector;
-import org.bouncycastle.jcajce.PKIXCertStore;
 import org.bouncycastle.jcajce.PKIXCertStoreSelector;
 import org.bouncycastle.jcajce.PKIXExtendedParameters;
 import org.bouncycastle.jcajce.util.JcaJceHelper;
@@ -69,257 +55,34 @@
 
 class RevocationUtilities
 {
-    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
-
-    protected static final String CERTIFICATE_POLICIES = Extension.certificatePolicies.getId();
-    protected static final String BASIC_CONSTRAINTS = Extension.basicConstraints.getId();
-    protected static final String POLICY_MAPPINGS = Extension.policyMappings.getId();
-    protected static final String SUBJECT_ALTERNATIVE_NAME = Extension.subjectAlternativeName.getId();
-    protected static final String NAME_CONSTRAINTS = Extension.nameConstraints.getId();
-    protected static final String KEY_USAGE = Extension.keyUsage.getId();
-    protected static final String INHIBIT_ANY_POLICY = Extension.inhibitAnyPolicy.getId();
     protected static final String ISSUING_DISTRIBUTION_POINT = Extension.issuingDistributionPoint.getId();
-    protected static final String DELTA_CRL_INDICATOR = Extension.deltaCRLIndicator.getId();
-    protected static final String POLICY_CONSTRAINTS = Extension.policyConstraints.getId();
-    protected static final String FRESHEST_CRL = Extension.freshestCRL.getId();
-    protected static final String CRL_DISTRIBUTION_POINTS = Extension.cRLDistributionPoints.getId();
-    protected static final String AUTHORITY_KEY_IDENTIFIER = Extension.authorityKeyIdentifier.getId();
 
-    protected static final String ANY_POLICY = "2.5.29.32.0";
-
-    protected static final String CRL_NUMBER = Extension.cRLNumber.getId();
-
-    /*
-    * key usage bits
-    */
-    protected static final int KEY_CERT_SIGN = 5;
-    protected static final int CRL_SIGN = 6;
-
-    protected static final String[] crlReasons = new String[]{
-        "unspecified",
-        "keyCompromise",
-        "cACompromise",
-        "affiliationChanged",
-        "superseded",
-        "cessationOfOperation",
-        "certificateHold",
-        "unknown",
-        "removeFromCRL",
-        "privilegeWithdrawn",
-        "aACompromise"};
-
-    /**
-     * Search the given Set of TrustAnchor's for one that is the
-     * issuer of the given X509 certificate. Uses the default provider
-     * for signature verification.
-     *
-     * @param cert         the X509 certificate
-     * @param trustAnchors a Set of TrustAnchor's
-     * @return the <code>TrustAnchor</code> object if found or
-     *         <code>null</code> if not.
-     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
-     * on the given certificate has thrown an exception.
-     */
-    protected static TrustAnchor findTrustAnchor(
-        X509Certificate cert,
-        Set trustAnchors)
-        throws AnnotatedException
+    protected static Date getValidityDate(PKIXExtendedParameters paramsPKIX, Date currentDate)
     {
-        return findTrustAnchor(cert, trustAnchors, null);
+        Date validityDate = paramsPKIX.getValidityDate();
+
+        return null == validityDate ? currentDate : validityDate;
     }
 
     /**
-     * Search the given Set of TrustAnchor's for one that is the
-     * issuer of the given X509 certificate. Uses the specified
-     * provider for signature verification, or the default provider
-     * if null.
-     *
-     * @param cert         the X509 certificate
-     * @param trustAnchors a Set of TrustAnchor's
-     * @param sigProvider  the provider to use for signature verification
-     * @return the <code>TrustAnchor</code> object if found or
-     *         <code>null</code> if not.
-     * @throws AnnotatedException if a TrustAnchor was found but the signature verification
-     * on the given certificate has thrown an exception.
-     */
-    protected static TrustAnchor findTrustAnchor(
-        X509Certificate cert,
-        Set trustAnchors,
-        String sigProvider)
-        throws AnnotatedException
-    {
-        TrustAnchor trust = null;
-        PublicKey trustPublicKey = null;
-        Exception invalidKeyEx = null;
-
-        X509CertSelector certSelectX509 = new X509CertSelector();
-        X500Name certIssuer = getIssuer(cert);
-
-        try
-        {
-            certSelectX509.setSubject(certIssuer.getEncoded());
-        }
-        catch (IOException ex)
-        {
-            throw new AnnotatedException("Cannot set subject search criteria for trust anchor.", ex);
-        }
-
-        Iterator iter = trustAnchors.iterator();
-        while (iter.hasNext() && trust == null)
-        {
-            trust = (TrustAnchor)iter.next();
-            if (trust.getTrustedCert() != null)
-            {
-                if (certSelectX509.match(trust.getTrustedCert()))
-                {
-                    trustPublicKey = trust.getTrustedCert().getPublicKey();
-                }
-                else
-                {
-                    trust = null;
-                }
-            }
-            else if (trust.getCAName() != null
-                && trust.getCAPublicKey() != null)
-            {
-                try
-                {
-                    X500Name caName = getX500Name(trust.getCA());
-                    if (certIssuer.equals(caName))
-                    {
-                        trustPublicKey = trust.getCAPublicKey();
-                    }
-                    else
-                    {
-                        trust = null;
-                    }
-                }
-                catch (IllegalArgumentException ex)
-                {
-                    trust = null;
-                }
-            }
-            else
-            {
-                trust = null;
-            }
-
-            if (trustPublicKey != null)
-            {
-                try
-                {
-                    verifyX509Certificate(cert, trustPublicKey, sigProvider);
-                }
-                catch (Exception ex)
-                {
-                    invalidKeyEx = ex;
-                    trust = null;
-                    trustPublicKey = null;
-                }
-            }
-        }
-
-        if (trust == null && invalidKeyEx != null)
-        {
-            throw new AnnotatedException("TrustAnchor found but certificate validation failed.", invalidKeyEx);
-        }
-
-        return trust;
-    }
-
-    static boolean isIssuerTrustAnchor(
-        X509Certificate cert,
-        Set trustAnchors,
-        String sigProvider)
-        throws AnnotatedException
-    {
-        try
-        {
-            return findTrustAnchor(cert, trustAnchors, sigProvider) != null;
-        }
-        catch (Exception e)
-        {
-            return false;
-        }
-    }
-
-    static List<PKIXCertStore> getAdditionalStoresFromAltNames(
-        byte[] issuerAlternativeName,
-        Map<GeneralName, PKIXCertStore> altNameCertStoreMap)
-        throws CertificateParsingException
-    {
-        // if in the IssuerAltName extension an URI
-        // is given, add an additional X.509 store
-        if (issuerAlternativeName != null)
-        {
-            GeneralNames issuerAltName = GeneralNames.getInstance(ASN1OctetString.getInstance(issuerAlternativeName).getOctets());
-
-            GeneralName[] names = issuerAltName.getNames();
-            List<PKIXCertStore>  stores = new ArrayList<PKIXCertStore>();
-
-            for (int i = 0; i != names.length; i++)
-            {
-                GeneralName altName = names[i];
-
-                PKIXCertStore altStore = altNameCertStoreMap.get(altName);
-
-                if (altStore != null)
-                {
-                    stores.add(altStore);
-                }
-            }
-
-            return stores;
-        }
-        else
-        {
-            return Collections.EMPTY_LIST;
-        }
-    }
-
-    protected static Date getValidDate(PKIXExtendedParameters paramsPKIX)
-    {
-        Date validDate = paramsPKIX.getDate();
-
-        if (validDate == null)
-        {
-            validDate = new Date();
-        }
-
-        return validDate;
-    }
-
-    protected static boolean isSelfIssued(X509Certificate cert)
-    {
-        return cert.getSubjectDN().equals(cert.getIssuerDN());
-    }
-
-
-    /**
      * Extract the value of the given extension, if it exists.
      *
-     * @param ext The extension object.
-     * @param oid The object identifier to obtain.
-     * @throws AnnotatedException if the extension cannot be read.
+     * @param ext
+     *            The extension object.
+     * @param oid
+     *            The object identifier to obtain.
+     * @throws AnnotatedException
+     *             if the extension cannot be read.
      */
-    protected static ASN1Primitive getExtensionValue(
-        java.security.cert.X509Extension ext,
-        ASN1ObjectIdentifier oid)
+    protected static ASN1Primitive getExtensionValue(java.security.cert.X509Extension ext, ASN1ObjectIdentifier oid)
         throws AnnotatedException
     {
         byte[] bytes = ext.getExtensionValue(oid.getId());
-        if (bytes == null)
-        {
-            return null;
-        }
 
-        return getObject(oid, bytes);
+        return null == bytes ? null : getObject(oid, bytes);
     }
 
-    private static ASN1Primitive getObject(
-        ASN1ObjectIdentifier oid,
-        byte[] ext)
-        throws AnnotatedException
+    private static ASN1Primitive getObject(ASN1ObjectIdentifier oid, byte[] ext) throws AnnotatedException
     {
         try
         {
@@ -331,44 +94,24 @@
         }
     }
 
-    protected static AlgorithmIdentifier getAlgorithmIdentifier(
-        PublicKey key)
-        throws CertPathValidatorException
-    {
-        try
-        {
-            ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
-
-            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
-
-            return info.getAlgorithm();
-        }
-        catch (Exception e)
-        {
-            throw new CertPathValidatorException("subject public key cannot be decoded", e);
-        }
-    }
-
-    // crl checking
-
     /**
-     * Return a Collection of all certificates or attribute certificates found
-     * in the X509Store's that are matching the certSelect criteriums.
+     * Add to a LinkedHashSet all certificates or attribute certificates found in the X509Store's
+     * that are matching the certSelect criteria.
      *
-     * @param certSelect a {@link Selector} object that will be used to select
-     *                   the certificates
-     * @param certStores a List containing only {@link Store} objects. These
-     *                   are used to search for certificates.
-     * @return a Collection of all found {@link X509Certificate}
-     *         May be empty but never <code>null</code>.
+     * @param certs
+     *            a {@link LinkedHashSet} to which the certificates will be added.
+     * @param certSelect
+     *            a {@link Selector} object that will be used to select the certificates
+     * @param certStores
+     *            a List containing only {@link Store} objects. These are used to search for
+     *            certificates.
+     * @return a Collection of all found {@link X509Certificate} May be empty but never
+     *         <code>null</code>.
      */
-    protected static Collection findCertificates(PKIXCertStoreSelector certSelect,
-                                                 List certStores)
+    protected static void findCertificates(LinkedHashSet certs, PKIXCertStoreSelector certSelect, List certStores)
         throws AnnotatedException
     {
-        Set certs = new LinkedHashSet();
         Iterator iter = certStores.iterator();
-
         while (iter.hasNext())
         {
             Object obj = iter.next();
@@ -382,75 +125,64 @@
                 }
                 catch (StoreException e)
                 {
-                    throw new AnnotatedException(
-                            "Problem while picking certificates from X.509 store.", e);
+                    throw new AnnotatedException("Problem while picking certificates from X.509 store.", e);
                 }
             }
             else
             {
                 CertStore certStore = (CertStore)obj;
-
                 try
                 {
                     certs.addAll(PKIXCertStoreSelector.getCertificates(certSelect, certStore));
                 }
                 catch (CertStoreException e)
                 {
-                    throw new AnnotatedException(
-                        "Problem while picking certificates from certificate store.",
-                        e);
+                    throw new AnnotatedException("Problem while picking certificates from certificate store.", e);
                 }
             }
         }
-        return certs;
     }
 
-    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(CRLDistPoint crldp, Map<GeneralName, PKIXCRLStore> namedCRLStoreMap)
-        throws AnnotatedException
+    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(CRLDistPoint crldp,
+        Map<GeneralName, PKIXCRLStore> namedCRLStoreMap) throws AnnotatedException
     {
-        if (crldp != null)
+        if (crldp == null)
         {
-            DistributionPoint dps[] = null;
-            try
-            {
-                dps = crldp.getDistributionPoints();
-            }
-            catch (Exception e)
-            {
-                throw new AnnotatedException(
-                    "Distribution points could not be read.", e);
-            }
-            List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
+            return Collections.emptyList();
+        }
 
-            for (int i = 0; i < dps.length; i++)
+        DistributionPoint dps[];
+        try
+        {
+            dps = crldp.getDistributionPoints();
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException("Distribution points could not be read.", e);
+        }
+
+        List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
+
+        for (int i = 0; i < dps.length; i++)
+        {
+            DistributionPointName dpn = dps[i].getDistributionPoint();
+            // look for URIs in fullName
+            if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
             {
-                DistributionPointName dpn = dps[i].getDistributionPoint();
-                // look for URIs in fullName
-                if (dpn != null)
+                GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
+
+                for (int j = 0; j < genNames.length; j++)
                 {
-                    if (dpn.getType() == DistributionPointName.FULL_NAME)
+                    PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
+                    if (store != null)
                     {
-                        GeneralName[] genNames = GeneralNames.getInstance(
-                            dpn.getName()).getNames();
-
-                        for (int j = 0; j < genNames.length; j++)
-                        {
-                            PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
-                            if (store != null)
-                            {
-                                stores.add(store);
-                            }
-                        }
+                        stores.add(store);
                     }
                 }
             }
+        }
 
-            return stores;
-        }
-        else
-        {
-            return Collections.EMPTY_LIST;
-        }
+        return stores;
     }
 
     /**
@@ -469,11 +201,8 @@
      * @throws ClassCastException if <code>issuerPrincipals</code> does not
      * contain only <code>X500Name</code>s.
      */
-    protected static void getCRLIssuersFromDistributionPoint(
-        DistributionPoint dp,
-        Collection issuerPrincipals,
-        X509CRLSelector selector)
-        throws AnnotatedException
+    protected static void getCRLIssuersFromDistributionPoint(DistributionPoint dp, Collection issuerPrincipals,
+        X509CRLSelector selector) throws AnnotatedException
     {
         List issuers = new ArrayList();
         // indirect CRL
@@ -573,11 +302,7 @@
         }
     }
 
-    protected static void getCertStatus(
-        Date validDate,
-        X509CRL crl,
-        Object cert,
-        CertStatus certStatus)
+    protected static void getCertStatus(Date validDate, X509CRL crl, Object cert, CertStatus certStatus)
         throws AnnotatedException
     {
         boolean isIndirect;
@@ -671,15 +396,16 @@
     /**
      * Fetches delta CRLs according to RFC 3280 section 5.2.4.
      *
-     * @param validityDate The date for which the delta CRLs must be valid.
-     * @param completeCRL The complete CRL the delta CRL is for.
+     * @param validityDate
+     *            The date for which the delta CRLs must be valid.
+     * @param completeCRL
+     *            The complete CRL the delta CRL is for.
      * @return A <code>Set</code> of <code>X509CRL</code>s with delta CRLs.
-     * @throws AnnotatedException if an exception occurs while picking the delta
-     * CRLs.
+     * @throws AnnotatedException
+     *             if an exception occurs while picking the delta CRLs.
      */
-    protected static Set getDeltaCRLs(Date validityDate,
-                                      X509CRL completeCRL, List<CertStore> certStores, List<PKIXCRLStore> pkixCrlStores)
-        throws AnnotatedException
+    protected static Set getDeltaCRLs(Date validityDate, X509CRL completeCRL, List<CertStore> certStores,
+        List<PKIXCRLStore> pkixCrlStores) throws AnnotatedException
     {
         X509CRLSelector baseDeltaSelect = new X509CRLSelector();
         // 5.2.4 (a)
@@ -695,8 +421,7 @@
         BigInteger completeCRLNumber = null;
         try
         {
-            ASN1Primitive derObject = RevocationUtilities.getExtensionValue(completeCRL,
-                Extension.cRLNumber);
+            ASN1Primitive derObject = RevocationUtilities.getExtensionValue(completeCRL, Extension.cRLNumber);
             if (derObject != null)
             {
                 completeCRLNumber = ASN1Integer.getInstance(derObject).getPositiveValue();
@@ -709,16 +434,14 @@
         }
 
         // 5.2.4 (b)
-        byte[] idp = null;
+        byte[] idp;
         try
         {
             idp = completeCRL.getExtensionValue(ISSUING_DISTRIBUTION_POINT);
         }
         catch (Exception e)
         {
-            throw new AnnotatedException(
-                "issuing distribution point extension value could not be read",
-                e);
+            throw new AnnotatedException("issuing distribution point extension value could not be read", e);
         }
 
         // 5.2.4 (d)
@@ -737,7 +460,7 @@
         PKIXCRLStoreSelector deltaSelect = selBuilder.build();
 
         // find delta CRLs
-        Set temp = CRL_UTIL.findCRLs(deltaSelect, validityDate, certStores, pkixCrlStores);
+        Set temp = PKIXCRLUtil.findCRLs(deltaSelect, validityDate, certStores, pkixCrlStores);
 
         Set result = new HashSet();
 
@@ -758,24 +481,19 @@
     {
         Set critical = crl.getCriticalExtensionOIDs();
 
-        if (critical == null)
-        {
-            return false;
-        }
-
-        return critical.contains(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+        return null == critical ? false : critical.contains(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
     }
 
     /**
      * Fetches complete CRLs according to RFC 3280.
      *
-     * @param dp          The distribution point for which the complete CRL
-     * @param cert        The <code>X509Certificate</code> for
-     *                    which the CRL should be searched.
-     * @return A <code>Set</code> of <code>X509CRL</code>s with complete
-     *         CRLs.
-     * @throws AnnotatedException if an exception occurs while picking the CRLs
-     * or no CRLs are found.
+     * @param dp
+     *            The distribution point for which the complete CRL
+     * @param cert
+     *            The <code>X509Certificate</code> for which the CRL should be searched.
+     * @return A <code>Set</code> of <code>X509CRL</code>s with complete CRLs.
+     * @throws AnnotatedException
+     *             if an exception occurs while picking the CRLs or no CRLs are found.
      */
     protected static Set getCompleteCRLs(DistributionPoint dp, Object cert, Date validityDate, List certStores, List crlStores)
         throws AnnotatedException, CRLNotFoundException
@@ -802,77 +520,13 @@
 
         PKIXCRLStoreSelector crlSelect = new PKIXCRLStoreSelector.Builder(baseCrlSelect).setCompleteCRLEnabled(true).build();
 
-        Set crls = CRL_UTIL.findCRLs(crlSelect, validityDate, certStores, crlStores);
+        Set crls = PKIXCRLUtil.findCRLs(crlSelect, validityDate, certStores, crlStores);
 
         checkCRLsNotEmpty(crls, cert);
 
         return crls;
     }
 
-    protected static Date getValidCertDateFromValidityModel(
-        PKIXExtendedParameters paramsPKIX, CertPath certPath, int index)
-        throws AnnotatedException
-    {
-        if (paramsPKIX.getValidityModel() == PKIXExtendedParameters.CHAIN_VALIDITY_MODEL)
-        {
-            // if end cert use given signing/encryption/... time
-            if (index <= 0)
-            {
-                return RevocationUtilities.getValidDate(paramsPKIX);
-                // else use time when previous cert was created
-            }
-            else
-            {
-                if (index - 1 == 0)
-                {
-                    ASN1GeneralizedTime dateOfCertgen = null;
-                    try
-                    {
-                        byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1)).getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
-                        if (extBytes != null)
-                        {
-                            dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
-                        }
-                    }
-                    catch (IOException e)
-                    {
-                        throw new AnnotatedException(
-                            "Date of cert gen extension could not be read.");
-                    }
-                    catch (IllegalArgumentException e)
-                    {
-                        throw new AnnotatedException(
-                            "Date of cert gen extension could not be read.");
-                    }
-                    if (dateOfCertgen != null)
-                    {
-                        try
-                        {
-                            return dateOfCertgen.getDate();
-                        }
-                        catch (ParseException e)
-                        {
-                            throw new AnnotatedException(
-                                "Date from date of cert gen extension could not be parsed.",
-                                e);
-                        }
-                    }
-                    return ((X509Certificate)certPath.getCertificates().get(
-                        index - 1)).getNotBefore();
-                }
-                else
-                {
-                    return ((X509Certificate)certPath.getCertificates().get(
-                        index - 1)).getNotBefore();
-                }
-            }
-        }
-        else
-        {
-            return getValidDate(paramsPKIX);
-        }
-    }
-
     /**
      * Return the next working key inheriting DSA parameters if necessary.
      * <p>
@@ -938,94 +592,6 @@
         throw new CertPathValidatorException("DSA parameters cannot be inherited from previous certificate.");
     }
 
-    /**
-     * Find the issuer certificates of a given certificate.
-     *
-     * @param cert       The certificate for which an issuer should be found.
-     * @return A <code>Collection</code> object containing the issuer
-     *         <code>X509Certificate</code>s. Never <code>null</code>.
-     * @throws AnnotatedException if an error occurs.
-     */
-    static Collection findIssuerCerts(
-        X509Certificate cert,
-        List<CertStore> certStores,
-        List<PKIXCertStore> pkixCertStores)
-        throws AnnotatedException
-    {
-        X509CertSelector selector = new X509CertSelector();
-
-        try
-        {
-            selector.setSubject(((X509Certificate)cert).getIssuerX500Principal().getEncoded());
-        }
-        catch (IOException e)
-        {
-            throw new AnnotatedException(
-                           "Subject criteria for certificate selector to find issuer certificate could not be set.", e);
-        }
-
-        try
-        {
-            byte[] akiExtensionValue = cert.getExtensionValue(AUTHORITY_KEY_IDENTIFIER);
-            if (akiExtensionValue != null)
-            {
-                ASN1OctetString aki = ASN1OctetString.getInstance(akiExtensionValue);
-                byte[] authorityKeyIdentifier = AuthorityKeyIdentifier.getInstance(aki.getOctets()).getKeyIdentifier();
-                if (authorityKeyIdentifier != null)
-                {
-                    selector.setSubjectKeyIdentifier(new DEROctetString(authorityKeyIdentifier).getEncoded());
-                }
-            }
-        }
-        catch (Exception e)
-        {
-            // authority key identifier could not be retrieved from target cert, just search without it
-        }
-
-        PKIXCertStoreSelector certSelect = new PKIXCertStoreSelector.Builder(selector).build();
-        Set certs = new LinkedHashSet();
-
-        Iterator iter;
-
-        try
-        {
-            List matches = new ArrayList();
-
-            matches.addAll(RevocationUtilities.findCertificates(certSelect, certStores));
-            matches.addAll(RevocationUtilities.findCertificates(certSelect, pkixCertStores));
-
-            iter = matches.iterator();
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Issuer certificate cannot be searched.", e);
-        }
-
-        X509Certificate issuer = null;
-        while (iter.hasNext())
-        {
-            issuer = (X509Certificate)iter.next();
-            // issuer cannot be verified because possible DSA inheritance
-            // parameters are missing
-            certs.add(issuer);
-        }
-        return certs;
-    }
-
-    protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
-                                                String sigProvider)
-        throws GeneralSecurityException
-    {
-        if (sigProvider == null)
-        {
-            cert.verify(publicKey);
-        }
-        else
-        {
-            cert.verify(publicKey, sigProvider);
-        }
-    }
-
     static void checkCRLsNotEmpty(Set crls, Object cert)
         throws CRLNotFoundException
     {
@@ -1047,8 +613,7 @@
         }
     }
 
-    public static boolean isIndirectCRL(X509CRL crl)
-        throws CRLException
+    public static boolean isIndirectCRL(X509CRL crl) throws CRLException
     {
         try
         {
@@ -1058,8 +623,7 @@
         }
         catch (Exception e)
         {
-            throw new CRLException(
-                    "exception reading IssuingDistributionPoint", e);
+            throw new CRLException("exception reading IssuingDistributionPoint", e);
         }
     }
 
diff --git a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java
index e1315c3..fd6b907 100644
--- a/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java
+++ b/bcpkix/src/main/java/org/bouncycastle/pkix/jcajce/X509RevocationChecker.java
@@ -70,7 +70,7 @@
      * of this: The PKIX model and the modified PKIX model. The PKIX model
      * verifies that all involved certificates must have been valid at the
      * current time. The modified PKIX model verifies that all involved
-     * certificates were valid at the signing time. Both are indirectly choosen
+     * certificates were valid at the time of signing. Both are indirectly chosen
      * with the {@link PKIXParameters#setDate(Date)} method, so this
      * methods sets the Date when <em>all</em> certificates must have been
      * valid.
@@ -224,6 +224,19 @@
         }
 
         /**
+         * @param validityModel
+         *            The validity model to set.
+         * @see #CHAIN_VALIDITY_MODEL
+         * @see #PKIX_VALIDITY_MODEL
+         */
+        public Builder setValidityModel(int validityModel)
+        {
+            this.validityModel = validityModel;
+
+            return this;
+        }
+
+        /**
          * Configure to use the installed provider with name ProviderName.
          *
          * @param provider provider to use.
@@ -267,6 +280,7 @@
     private final Map<X500Principal, Long> failures = new HashMap<X500Principal, Long>();
     private final Set<TrustAnchor> trustAnchors;
     private final boolean isCheckEEOnly;
+    private final int validityModel;
     private final List<Store<CRL>> crls;
     private final List<CertStore> crlCertStores;
     private final JcaJceHelper helper;
@@ -274,6 +288,7 @@
     private final long failLogMaxTime;
     private final long failHardMaxTime;
 
+    private Date currentDate;
     private X500Principal workingIssuerName;
     private PublicKey workingPublicKey;
     private X509Certificate signingCert;
@@ -283,6 +298,7 @@
         this.crls = new ArrayList<Store<CRL>>(bldr.crls);
         this.crlCertStores = new ArrayList<CertStore>(bldr.crlCertStores);
         this.isCheckEEOnly = bldr.isCheckEEOnly;
+        this.validityModel = bldr.validityModel;
         this.trustAnchors = bldr.trustAnchors;
         this.canSoftFail = bldr.canSoftFail;
         this.failLogMaxTime = bldr.failLogMaxTime;
@@ -294,11 +310,11 @@
         }
         else if (bldr.providerName != null)
         {
-            helper = new NamedJcaJceHelper(bldr.providerName);
+            this.helper = new NamedJcaJceHelper(bldr.providerName);
         }
         else
         {
-            helper = new DefaultJcaJceHelper();
+            this.helper = new DefaultJcaJceHelper();
         }
     }
 
@@ -309,6 +325,8 @@
         {
             throw new IllegalArgumentException("forward processing not supported");
         }
+
+        this.currentDate = new Date();
         this.workingIssuerName = null;
     }
 
@@ -362,39 +380,39 @@
             this.workingPublicKey = signingCert.getPublicKey();
         }
 
-        PKIXParameters baseParams;
         List<X500Principal> issuerList = new ArrayList<X500Principal>();
 
+        PKIXExtendedParameters.Builder pkixBuilder;
         try
         {
-            baseParams = new PKIXParameters(trustAnchors);
+            PKIXParameters pkixParams = new PKIXParameters(trustAnchors);
+            pkixParams.setRevocationEnabled(false);
+            pkixParams.setDate(currentDate);
 
-            baseParams.setRevocationEnabled(false);
-            baseParams.setDate(new Date());
-            
             for (int i = 0; i != crlCertStores.size(); i++)
             {
                 if (LOG.isLoggable(Level.INFO))
                 {
                     addIssuers(issuerList, crlCertStores.get(i));
                 }
-                baseParams.addCertStore(crlCertStores.get(i));
+                pkixParams.addCertStore(crlCertStores.get(i));
             }
+
+            pkixBuilder = new PKIXExtendedParameters.Builder(pkixParams);
+            pkixBuilder.setValidityModel(validityModel);
         }
         catch (GeneralSecurityException e)
         {
             throw new RuntimeException("error setting up baseParams: " + e.getMessage());
         }
 
-        PKIXExtendedParameters.Builder pkixParamsBldr = new PKIXExtendedParameters.Builder(baseParams);
-
         for (int i = 0; i != crls.size(); i++)
         {
             if (LOG.isLoggable(Level.INFO))
             {
                 addIssuers(issuerList, crls.get(i));
             }
-            pkixParamsBldr.addCRLStore(new LocalCRLStore(crls.get(i)));
+            pkixBuilder.addCRLStore(new LocalCRLStore(crls.get(i)));
         }
 
         if (issuerList.isEmpty())
@@ -415,10 +433,14 @@
                 LOG.log(Level.INFO, "configured with " + issuerList.size() + " pre-loaded CRLs");
             }
         }
-        
+
+        PKIXExtendedParameters pkixParams = pkixBuilder.build();
+
+        Date validityDate = RevocationUtilities.getValidityDate(pkixParams, currentDate);
+
         try
         {
-            checkCRLs(pkixParamsBldr.build(), cert, baseParams.getDate(), signingCert, workingPublicKey, new ArrayList(), helper);
+            checkCRLs(pkixParams, currentDate, validityDate, cert, signingCert, workingPublicKey, new ArrayList(), helper);
         }
         catch (AnnotatedException e)
         {
@@ -426,68 +448,70 @@
         }
         catch (CRLNotFoundException e)
         {
-            if (cert.getExtensionValue(Extension.cRLDistributionPoints.getId()) != null)
+            if (null == cert.getExtensionValue(Extension.cRLDistributionPoints.getId()))
             {
-                CRL crl = null;
+                throw e;
+            }
+
+            CRL crl;
+            try
+            {
+                crl = downloadCRLs(cert.getIssuerX500Principal(), currentDate,
+                    RevocationUtilities.getExtensionValue(cert, Extension.cRLDistributionPoints), helper);
+            }
+            catch(AnnotatedException e1)
+            {
+                throw new CertPathValidatorException(e.getMessage(), e.getCause());
+            }
+
+            if (crl != null)
+            {
                 try
                 {
-                    crl = downloadCRLs(cert.getIssuerX500Principal(), baseParams.getDate(), RevocationUtilities.getExtensionValue(cert, Extension.cRLDistributionPoints), helper);
+                    pkixBuilder.addCRLStore(new LocalCRLStore(new CollectionStore<CRL>(Collections.singleton(crl))));
+
+                    pkixParams = pkixBuilder.build();
+
+                    validityDate = RevocationUtilities.getValidityDate(pkixParams, currentDate);
+
+                    checkCRLs(pkixParams, currentDate, validityDate, cert, signingCert, workingPublicKey,
+                        new ArrayList(), helper);
                 }
                 catch(AnnotatedException e1)
                 {
                     throw new CertPathValidatorException(e.getMessage(), e.getCause());
                 }
-
-                if (crl != null)
-                {
-                    try
-                    {
-                        pkixParamsBldr.addCRLStore(new LocalCRLStore(
-                            new CollectionStore<CRL>(Collections.singleton(crl))));
-                        checkCRLs(pkixParamsBldr.build(), cert, new Date(), signingCert, workingPublicKey, new ArrayList(), helper);
-                    }
-                    catch(AnnotatedException e1)
-                    {
-                        throw new CertPathValidatorException(e.getMessage(), e.getCause());
-                    }
-                }
-                else
-                {
-                    if (canSoftFail)
-                    {
-                        X500Principal issuer = cert.getIssuerX500Principal();
-
-                        Long initial = failures.get(issuer);
-                        if (initial != null)
-                        {
-                             long period = System.currentTimeMillis() - initial.longValue();
-                             if (failHardMaxTime != -1 && failHardMaxTime < period)
-                             {
-                                 throw e;
-                             }
-                             if (period < failLogMaxTime)
-                             {
-                                 LOG.log(Level.WARNING, "soft failing for issuer: \"" + issuer + "\"");
-                             }
-                             else
-                             {
-                                 LOG.log(Level.SEVERE, "soft failing for issuer: \"" + issuer + "\"");
-                             }
-                        }
-                        else
-                        {
-                            failures.put(issuer, System.currentTimeMillis());
-                        }
-                    }
-                    else
-                    {
-                        throw e;
-                    }
-                }
             }
             else
             {
-                throw e;
+                if (!canSoftFail)
+                {
+                    throw e;
+                }
+
+                X500Principal issuer = cert.getIssuerX500Principal();
+
+                Long initial = failures.get(issuer);
+                if (initial != null)
+                {
+                     long period = System.currentTimeMillis() - initial.longValue();
+                     if (failHardMaxTime != -1 && failHardMaxTime < period)
+                     {
+                         throw e;
+                     }
+                     if (period < failLogMaxTime)
+                     {
+                         LOG.log(Level.WARNING, "soft failing for issuer: \"" + issuer + "\"");
+                     }
+                     else
+                     {
+                         LOG.log(Level.SEVERE, "soft failing for issuer: \"" + issuer + "\"");
+                     }
+                }
+                else
+                {
+                    failures.put(issuer, System.currentTimeMillis());
+                }
             }
         }
 
@@ -548,7 +572,7 @@
             DistributionPoint dp = points[i];
 
             DistributionPointName dpn = dp.getDistributionPoint();
-            if (dpn.getType() == DistributionPointName.FULL_NAME)
+            if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
             {
                 GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
 
@@ -623,59 +647,52 @@
         "privilegeWithdrawn",
         "aACompromise"};
 
-    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(CRLDistPoint crldp, Map<GeneralName, PKIXCRLStore> namedCRLStoreMap)
-        throws AnnotatedException
+    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(CRLDistPoint crldp,
+        Map<GeneralName, PKIXCRLStore> namedCRLStoreMap) throws AnnotatedException
     {
-        if (crldp != null)
+        if (crldp == null)
         {
-            DistributionPoint dps[] = null;
-            try
-            {
-                dps = crldp.getDistributionPoints();
-            }
-            catch (Exception e)
-            {
-                throw new AnnotatedException(
-                    "could not read distribution points could not be read", e);
-            }
-            List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
+            return Collections.emptyList();
+        }
 
-            for (int i = 0; i < dps.length; i++)
+        DistributionPoint dps[];
+        try
+        {
+            dps = crldp.getDistributionPoints();
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException("could not read distribution points could not be read", e);
+        }
+
+        List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
+
+        for (int i = 0; i < dps.length; i++)
+        {
+            DistributionPointName dpn = dps[i].getDistributionPoint();
+            // look for URIs in fullName
+            if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
             {
-                DistributionPointName dpn = dps[i].getDistributionPoint();
-                // look for URIs in fullName
-                if (dpn != null)
+                GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
+
+                for (int j = 0; j < genNames.length; j++)
                 {
-                    if (dpn.getType() == DistributionPointName.FULL_NAME)
+                    PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
+                    if (store != null)
                     {
-                        GeneralName[] genNames = GeneralNames.getInstance(
-                            dpn.getName()).getNames();
-
-                        for (int j = 0; j < genNames.length; j++)
-                        {
-                            PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
-                            if (store != null)
-                            {
-                                stores.add(store);
-                            }
-                        }
+                        stores.add(store);
                     }
                 }
             }
+        }
 
-            return stores;
-        }
-        else
-        {
-            return Collections.EMPTY_LIST;
-        }
+        return stores;
     }
 
-
     /**
      * Checks a certificate if it is revoked.
      *
-     * @param paramsPKIX       PKIX parameters.
+     * @param pkixParams       PKIX parameters.
      * @param cert             Certificate to check if it is revoked.
      * @param validDate        The date when the certificate revocation status should be
      *                         checked.
@@ -686,17 +703,17 @@
      * or some error occurs.
      */
     protected void checkCRLs(
-        PKIXExtendedParameters paramsPKIX,
+        PKIXExtendedParameters pkixParams,
+        Date currentDate,
+        Date validityDate,
         X509Certificate cert,
-        Date validDate,
         X509Certificate sign,
         PublicKey workingPublicKey,
         List certPathCerts,
         JcaJceHelper helper)
         throws AnnotatedException, CertPathValidatorException
     {
-        AnnotatedException lastException = null;
-        CRLDistPoint crldp = null;
+        CRLDistPoint crldp;
         try
         {
             crldp = CRLDistPoint.getInstance(RevocationUtilities.getExtensionValue(cert, Extension.cRLDistributionPoints));
@@ -706,29 +723,15 @@
             throw new AnnotatedException("cannot read CRL distribution point extension", e);
         }
 
-        PKIXExtendedParameters.Builder paramsBldr = new PKIXExtendedParameters.Builder(paramsPKIX);
-        try
-        {
-            List extras = getAdditionalStoresFromCRLDistributionPoint(crldp, paramsPKIX.getNamedCRLStoreMap());
-            for (Iterator it = extras.iterator(); it.hasNext(); )
-            {
-                paramsBldr.addCRLStore((PKIXCRLStore)it.next());
-            }
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException(
-                "no additional CRL locations could be decoded from CRL distribution point extension", e);
-        }
         CertStatus certStatus = new CertStatus();
         ReasonsMask reasonsMask = new ReasonsMask();
-        PKIXExtendedParameters finalParams = paramsBldr.build();
-
+        AnnotatedException lastException = null;
         boolean validCrlFound = false;
+
         // for each distribution point
         if (crldp != null)
         {
-            DistributionPoint dps[] = null;
+            DistributionPoint dps[];
             try
             {
                 dps = crldp.getDistributionPoints();
@@ -737,13 +740,33 @@
             {
                 throw new AnnotatedException("cannot read distribution points", e);
             }
+
             if (dps != null)
             {
+                PKIXExtendedParameters.Builder pkixBuilder = new PKIXExtendedParameters.Builder(pkixParams);
+                try
+                {
+                    List extras = getAdditionalStoresFromCRLDistributionPoint(crldp, pkixParams.getNamedCRLStoreMap());
+                    for (Iterator it = extras.iterator(); it.hasNext(); )
+                    {
+                        pkixBuilder.addCRLStore((PKIXCRLStore)it.next());
+                    }
+                }
+                catch (AnnotatedException e)
+                {
+                    throw new AnnotatedException(
+                        "no additional CRL locations could be decoded from CRL distribution point extension", e);
+                }
+
+                PKIXExtendedParameters pkixParamsFinal = pkixBuilder.build();
+                Date validityDateFinal = RevocationUtilities.getValidityDate(pkixParamsFinal, currentDate);
+
                 for (int i = 0; i < dps.length && certStatus.getCertStatus() == CertStatus.UNREVOKED && !reasonsMask.isAllReasons(); i++)
                 {
                     try
                     {
-                        RFC3280CertPathUtilities.checkCRL(dps[i], finalParams, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, certPathCerts, helper);
+                        RFC3280CertPathUtilities.checkCRL(dps[i], pkixParamsFinal, currentDate, validityDateFinal, cert,
+                            sign, workingPublicKey, certStatus, reasonsMask, certPathCerts, helper);
                         validCrlFound = true;
                     }
                     catch (AnnotatedException e)
@@ -773,9 +796,9 @@
 
                 DistributionPoint dp = new DistributionPoint(new DistributionPointName(0, new GeneralNames(
                     new GeneralName(GeneralName.directoryName, X500Name.getInstance(issuer.getEncoded())))), null, null);
-                PKIXExtendedParameters paramsPKIXClone = (PKIXExtendedParameters)paramsPKIX.clone();
-                RFC3280CertPathUtilities.checkCRL(dp, paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask,
-                    certPathCerts, helper);
+                PKIXExtendedParameters pkixParamsClone = (PKIXExtendedParameters)pkixParams.clone();
+                RFC3280CertPathUtilities.checkCRL(dp, pkixParamsClone, currentDate, validityDate, cert, sign,
+                    workingPublicKey, certStatus, reasonsMask, certPathCerts, helper);
                 validCrlFound = true;
             }
             catch (AnnotatedException e)
@@ -817,8 +840,8 @@
         return this;
     }
 
-    private class LocalCRLStore<T extends CRL>
-        implements PKIXCRLStore, Iterable<CRL>
+    private class LocalCRLStore
+        implements PKIXCRLStore<CRL>, Iterable<CRL>
     {
         private Collection<CRL> _local;
 
@@ -827,8 +850,7 @@
          *
          * @param collection - initial contents for the store, this is copied.
          */
-        public LocalCRLStore(
-            Store<CRL> collection)
+        public LocalCRLStore(Store<CRL> collection)
         {
             _local = new ArrayList<CRL>(collection.getMatches(null));
         }
@@ -839,29 +861,27 @@
          * @param selector the selector to match against.
          * @return a possibly empty collection of matching objects.
          */
-        public Collection getMatches(Selector selector)
+        public Collection<CRL> getMatches(Selector<CRL> selector)
         {
             if (selector == null)
             {
                 return new ArrayList<CRL>(_local);
             }
-            else
+
+            List<CRL> col = new ArrayList<CRL>();
+            Iterator<CRL> iter = _local.iterator();
+
+            while (iter.hasNext())
             {
-                List<CRL> col = new ArrayList<CRL>();
-                Iterator<CRL> iter = _local.iterator();
+                CRL obj = iter.next();
 
-                while (iter.hasNext())
+                if (selector.match(obj))
                 {
-                    CRL obj = iter.next();
-
-                    if (selector.match(obj))
-                    {
-                        col.add(obj);
-                    }
+                    col.add(obj);
                 }
-
-                return col;
             }
+
+            return col;
         }
 
         public Iterator<CRL> iterator()
diff --git a/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampToken.java b/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampToken.java
index 6e9132a..9918456 100644
--- a/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampToken.java
+++ b/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampToken.java
@@ -6,6 +6,7 @@
 import java.util.Collection;
 import java.util.Date;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.cms.Attribute;
 import org.bouncycastle.asn1.cms.AttributeTable;
@@ -315,7 +316,18 @@
     public byte[] getEncoded() 
         throws IOException
     {
-        return tsToken.getEncoded();
+        return tsToken.getEncoded(ASN1Encoding.DL);
+    }
+
+    /**
+     * return the ASN.1 encoded representation of this object using the specified encoding.
+     *
+     * @param encoding the ASN.1 encoding format to use ("BER", "DL", or "DER").
+     */
+    public byte[] getEncoded(String encoding)
+        throws IOException
+    {
+        return tsToken.getEncoded(encoding);
     }
 
     // perhaps this should be done using an interface on the ASN.1 classes...
diff --git a/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampTokenGenerator.java b/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampTokenGenerator.java
index 6577d19..69a52ed 100644
--- a/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampTokenGenerator.java
+++ b/bcpkix/src/main/java/org/bouncycastle/tsp/TimeStampTokenGenerator.java
@@ -88,7 +88,12 @@
     public static final int R_TENTHS_OF_SECONDS = 1;
 
     /**
-     * Create time-stamps with a resolution of 1 microsecond.
+     * Create time-stamps with a resolution of 1 hundredth of a second.
+     */
+    public static final int R_HUNDREDTHS_OF_SECONDS = 2;
+
+    /**
+     * @deprecated use R_HUNDREDTHS_OF_SECONDS - this field will be deleted!!
      */
     public static final int R_MICROSECONDS = 2;
 
@@ -504,7 +509,7 @@
                 sBuild.delete(dotIndex + 2, sBuild.length());
             }
             break;
-        case R_MICROSECONDS:
+        case R_HUNDREDTHS_OF_SECONDS:
             if (sBuild.length() > dotIndex + 3)
             {
                 sBuild.delete(dotIndex + 3, sBuild.length());
diff --git a/bcpkix/src/main/java/org/bouncycastle/tsp/test/NewTSPTest.java b/bcpkix/src/main/java/org/bouncycastle/tsp/test/NewTSPTest.java
index c7fef3f..912e4d5 100644
--- a/bcpkix/src/main/java/org/bouncycastle/tsp/test/NewTSPTest.java
+++ b/bcpkix/src/main/java/org/bouncycastle/tsp/test/NewTSPTest.java
@@ -107,7 +107,7 @@
         basicTest(origKP.getPrivate(), origCert, certs);
         resolutionTest(origKP.getPrivate(), origCert, certs, TimeStampTokenGenerator.R_SECONDS, "19700101000009Z");
         resolutionTest(origKP.getPrivate(), origCert, certs, TimeStampTokenGenerator.R_TENTHS_OF_SECONDS, "19700101000009.9Z");
-        resolutionTest(origKP.getPrivate(), origCert, certs, TimeStampTokenGenerator.R_MICROSECONDS, "19700101000009.99Z");
+        resolutionTest(origKP.getPrivate(), origCert, certs, TimeStampTokenGenerator.R_HUNDREDTHS_OF_SECONDS, "19700101000009.99Z");
         resolutionTest(origKP.getPrivate(), origCert, certs, TimeStampTokenGenerator.R_MILLISECONDS, "19700101000009.999Z");
         basicSha256Test(origKP.getPrivate(), origCert, certs);
         basicTestWithTSA(origKP.getPrivate(), origCert, certs);
@@ -335,7 +335,7 @@
 
         assertEquals("19700101000009Z", tsToken.getTimeStampInfo().toASN1Structure().getGenTime().getTimeString());
 
-        if (resolution > TimeStampTokenGenerator.R_MICROSECONDS)
+        if (resolution > TimeStampTokenGenerator.R_HUNDREDTHS_OF_SECONDS)
         {
             tsResp = tsRespGen.generate(request, new BigInteger("23"), new Date(9990L));
             tsToken = tsResp.getTimeStampToken();
diff --git a/bcprov/src/main/java/org/bouncycastle/LICENSE.java b/bcprov/src/main/java/org/bouncycastle/LICENSE.java
index db8c6f1..1db15d2 100644
--- a/bcprov/src/main/java/org/bouncycastle/LICENSE.java
+++ b/bcprov/src/main/java/org/bouncycastle/LICENSE.java
@@ -5,7 +5,7 @@
 /**
  * The Bouncy Castle License
  *
- * Copyright (c) 2000-2019 The Legion Of The Bouncy Castle Inc. (http://www.bouncycastle.org)
+ * Copyright (c) 2000-2021 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org)
  * <p>
  * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
  * and associated documentation files (the "Software"), to deal in the Software without restriction, 
@@ -26,7 +26,7 @@
 public class LICENSE
 {
     public static final String licenseText =
-      "Copyright (c) 2000-2019 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) "
+      "Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) "
       + Strings.lineSeparator()
       + Strings.lineSeparator()
       + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software "
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1BitString.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1BitString.java
index 4da3dca..95abee1 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1BitString.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1BitString.java
@@ -203,7 +203,15 @@
 
     public byte[] getBytes()
     {
-        return derForm(data, padBits);
+        if (0 == data.length)
+        {
+            return data;
+        }
+
+        byte[] rv = Arrays.clone(data);
+        // DER requires pad bits be zero
+        rv[data.length - 1] &= (0xFF << padBits);
+        return rv;
     }
 
     public int getPadBits()
@@ -269,21 +277,6 @@
         return derA == derB;
     }
 
-    /**
-     * @deprecated Will be hidden/removed.
-     */
-    protected static byte[] derForm(byte[] data, int padBits)
-    {
-        if (0 == data.length)
-        {
-            return data;
-        }
-        byte[] rv = Arrays.clone(data);
-        // DER requires pad bits be zero
-        rv[data.length - 1] &= (0xFF << padBits);
-        return rv;
-    }
-
     static ASN1BitString fromInputStream(int length, InputStream stream)
         throws IOException
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1GeneralizedTime.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1GeneralizedTime.java
index 8f70705..1c52dfb 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1GeneralizedTime.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1GeneralizedTime.java
@@ -271,7 +271,7 @@
             }
         }
         catch (ParseException e)
-        {       e.printStackTrace();
+        {
             // we'll do our best and ignore daylight savings
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Generator.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Generator.java
index 3817d82..a9b9f5b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Generator.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Generator.java
@@ -7,12 +7,14 @@
  */
 public abstract class ASN1Generator
 {
+    // TODO This is problematic if we want an isolating buffer for all ASN.1 writes
     protected OutputStream _out;
 
     /**
      * Base constructor.
      *
-     * @param out the end output stream that object encodings are written to.
+     * @param out
+     *            the end output stream that object encodings are written to.
      */
     public ASN1Generator(OutputStream out)
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1InputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1InputStream.java
index 8abc2f6..ca8257b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1InputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1InputStream.java
@@ -383,23 +383,20 @@
         throws IOException
     {
         int len = defIn.getRemaining();
-        if (defIn.getRemaining() < tmpBuffers.length)
-        {
-            byte[] buf = tmpBuffers[len];
-
-            if (buf == null)
-            {
-                buf = tmpBuffers[len] = new byte[len];
-            }
-
-            Streams.readFully(defIn, buf);
-
-            return buf;
-        }
-        else
+        if (len >= tmpBuffers.length)
         {
             return defIn.toByteArray();
         }
+
+        byte[] buf = tmpBuffers[len];
+        if (buf == null)
+        {
+            buf = tmpBuffers[len] = new byte[len];
+        }
+
+        defIn.readAllIntoByteArray(buf);
+
+        return buf;
     }
 
     private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Object.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Object.java
index f5026a1..cd232cb 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Object.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Object.java
@@ -73,15 +73,6 @@
     }
 
     /**
-     * @deprecated use toASN1Primitive()
-     * @return the underlying primitive type.
-     */
-    public ASN1Primitive toASN1Object()
-    {
-        return this.toASN1Primitive();
-    }
-
-    /**
      * Return true if obj is a byte array and represents an object with the given tag value.
      *
      * @param obj object of interest.
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OctetString.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OctetString.java
index 9cce89a..59c6f34 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OctetString.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OctetString.java
@@ -133,7 +133,7 @@
          */
         if (taggedObject.isExplicit())
         {
-            ASN1OctetString singleSegment = ASN1OctetString.getInstance(o);
+            ASN1OctetString singleSegment = getInstance(o);
 
             if (taggedObject instanceof BERTaggedObject)
             {
@@ -194,7 +194,7 @@
         {
             try
             {
-                return ASN1OctetString.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
+                return getInstance(fromByteArray((byte[])obj));
             }
             catch (IOException e)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OutputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OutputStream.java
index 966793f..e6ae93f 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OutputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1OutputStream.java
@@ -264,16 +264,6 @@
         }
     }
 
-    /**
-     * @deprecated Will be removed.
-     */
-    protected void writeNull()
-        throws IOException
-    {
-        write(BERTags.NULL);
-        write(0x00);
-    }
-
     public void writeObject(ASN1Encodable obj) throws IOException
     {
         if (null == obj)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Sequence.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Sequence.java
index 86f4901..b64127a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Sequence.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Sequence.java
@@ -177,7 +177,7 @@
 
     /**
      * Create a SEQUENCE containing one object.
-     * @param obj the object to be put in the SEQUENCE.
+     * @param element the object to be put in the SEQUENCE.
      */
     protected ASN1Sequence(ASN1Encodable element)
     {
@@ -205,7 +205,7 @@
 
     /**
      * Create a SEQUENCE containing an array of objects.
-     * @param array the array of objects to be put in the SEQUENCE.
+     * @param elements the array of objects to be put in the SEQUENCE.
      */
     protected ASN1Sequence(ASN1Encodable[] elements)
     {
@@ -245,11 +245,11 @@
 
             public Object nextElement()
             {
-                if (pos >= elements.length)
+                if (pos < elements.length)
                 {
-                    throw new NoSuchElementException("ASN1Sequence Enumeration");
+                    return elements[pos++];
                 }
-                return elements[pos++];
+                throw new NoSuchElementException();
             }
         };
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Set.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Set.java
index 4e2ca73..fd676d7 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Set.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1Set.java
@@ -312,11 +312,11 @@
 
             public Object nextElement()
             {
-                if (pos >= elements.length)
+                if (pos < elements.length)
                 {
-                    throw new NoSuchElementException("ASN1Set Enumeration");
+                    return elements[pos++];
                 }
-                return elements[pos++];
+                throw new NoSuchElementException();
             }
         };
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1TaggedObject.java b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1TaggedObject.java
index f628f2d..00aac91 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ASN1TaggedObject.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ASN1TaggedObject.java
@@ -124,14 +124,6 @@
     }
 
     /**
-     * @deprecated Will be removed (always returns false).
-     */
-    public boolean isEmpty()
-    {
-        return false;
-    }
-
-    /**
      * Return whatever was following the tag.
      * <p>
      * Note: tagged objects are generally context dependent if you're
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/BERGenerator.java b/bcprov/src/main/java/org/bouncycastle/asn1/BERGenerator.java
index c855110..b492157 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/BERGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/BERGenerator.java
@@ -9,23 +9,20 @@
 public class BERGenerator
     extends ASN1Generator
 {
-    private boolean      _tagged = false;
-    private boolean      _isExplicit;
-    private int          _tagNo;
+    private boolean _tagged = false;
+    private boolean _isExplicit;
+    private int _tagNo;
 
-    protected BERGenerator(
-        OutputStream out)
+    protected BERGenerator(OutputStream out)
     {
         super(out);
     }
 
-    protected BERGenerator(
-        OutputStream out,
-        int tagNo,
-        boolean isExplicit) 
+    protected BERGenerator(OutputStream out, int tagNo, boolean isExplicit)
     {
         super(out);
-        
+
+        // TODO Check proper handling of implicit tagging
         _tagged = true;
         _isExplicit = isExplicit;
         _tagNo = tagNo;
@@ -35,18 +32,14 @@
     {
         return _out;
     }
-    
-    private void writeHdr(
-        int tag)
-        throws IOException
+
+    private void writeHdr(int tag) throws IOException
     {
         _out.write(tag);
         _out.write(0x80);
     }
-    
-    protected void writeBERHeader(
-        int tag) 
-        throws IOException
+
+    protected void writeBERHeader(int tag) throws IOException
     {
         if (_tagged)
         {
@@ -58,7 +51,7 @@
                 writeHdr(tag);
             }
             else
-            {   
+            {
                 if ((tag & BERTags.CONSTRUCTED) != 0)
                 {
                     writeHdr(tagNum | BERTags.CONSTRUCTED);
@@ -75,12 +68,11 @@
         }
     }
 
-    protected void writeBEREnd()
-        throws IOException
+    protected void writeBEREnd() throws IOException
     {
         _out.write(0x00);
         _out.write(0x00);
-        
+
         if (_tagged && _isExplicit)  // write extra end for tag header
         {
             _out.write(0x00);
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/BEROctetString.java b/bcprov/src/main/java/org/bouncycastle/asn1/BEROctetString.java
index c151f1d..3544c56 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/BEROctetString.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/BEROctetString.java
@@ -3,7 +3,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Enumeration;
-import java.util.Vector;
+import java.util.NoSuchElementException;
 
 /**
  * ASN.1 OctetStrings, with indefinite length rules, and <i>constructed form</i> support.
@@ -22,7 +22,7 @@
 public class BEROctetString
     extends ASN1OctetString
 {
-    private static final int DEFAULT_LENGTH = 1000;
+    private static final int DEFAULT_CHUNK_SIZE = 1000;
 
     private final int chunkSize;
     private final ASN1OctetString[] octs;
@@ -39,13 +39,7 @@
         {
             try
             {
-                DEROctetString o = (DEROctetString)octs[i];
-
-                bOut.write(o.getOctets());
-            }
-            catch (ClassCastException e)
-            {
-                throw new IllegalArgumentException(octs[i].getClass().getName() + " found in input should only contain DEROctetString");
+                bOut.write(octs[i].getOctets());
             }
             catch (IOException e)
             {
@@ -63,7 +57,7 @@
     public BEROctetString(
         byte[] string)
     {
-        this(string, DEFAULT_LENGTH);
+        this(string, DEFAULT_CHUNK_SIZE);
     }
 
     /**
@@ -75,7 +69,7 @@
     public BEROctetString(
         ASN1OctetString[] octs)
     {
-        this(octs, DEFAULT_LENGTH);
+        this(octs, DEFAULT_CHUNK_SIZE);
     }
 
     /**
@@ -112,15 +106,6 @@
     }
 
     /**
-     * Return a concatenated byte array of all the octets making up the constructed OCTET STRING
-     * @return the full OCTET STRING.
-     */
-    public byte[] getOctets()
-    {
-        return string;
-    }
-
-    /**
      * Return the OCTET STRINGs that make up this string.
      *
      * @return an Enumeration of the component OCTET STRINGs.
@@ -129,7 +114,28 @@
     {
         if (octs == null)
         {
-            return generateOcts().elements();
+            return new Enumeration()
+            {
+                int pos = 0;
+
+                public boolean hasMoreElements()
+                {
+                    return pos < string.length;
+                }
+
+                public Object nextElement()
+                {
+                    if (pos < string.length)
+                    {
+                        int length = Math.min(string.length - pos, chunkSize);
+                        byte[] chunk = new byte[length];
+                        System.arraycopy(string, pos, chunk, 0, length);
+                        pos += length;
+                        return new DEROctetString(chunk);
+                    }
+                    throw new NoSuchElementException();
+                }
+            };
         }
 
         return new Enumeration()
@@ -143,37 +149,15 @@
 
             public Object nextElement()
             {
-                return octs[counter++];
+                if (counter < octs.length)
+                {
+                    return octs[counter++];
+                }
+                throw new NoSuchElementException();
             }
         };
     }
 
-    private Vector generateOcts()
-    { 
-        Vector vec = new Vector();
-        for (int i = 0; i < string.length; i += chunkSize)
-        { 
-            int end; 
-
-            if (i + chunkSize > string.length)
-            { 
-                end = string.length; 
-            } 
-            else 
-            { 
-                end = i + chunkSize;
-            } 
-
-            byte[] nStr = new byte[end - i]; 
-
-            System.arraycopy(string, i, nStr, 0, nStr.length);
-
-            vec.addElement(new DEROctetString(nStr));
-         } 
-        
-         return vec; 
-    }
-
     boolean isConstructed()
     {
         return true;
@@ -191,15 +175,6 @@
         return 2 + length + 2;
     }
 
-    /**
-     * @deprecated
-     */
-    public void encode(ASN1OutputStream out)
-        throws IOException
-    {
-        out.writeObject(this);
-    }
-
     void encode(ASN1OutputStream out, boolean withTag) throws IOException
     {
         out.writeEncodedIndef(withTag, BERTags.CONSTRUCTED | BERTags.OCTET_STRING,  getObjects());
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/BEROutputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/BEROutputStream.java
index d8c4097..6bced5e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/BEROutputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/BEROutputStream.java
@@ -5,10 +5,8 @@
 /**
  * A class which writes indefinite and definite length objects. Objects which specify DER will be
  * encoded accordingly, but DL or BER objects will be encoded as defined.
- * 
- * @deprecated Will be removed from public API.
  */
-public class BEROutputStream
+class BEROutputStream
     extends ASN1OutputStream
 {
     /**
@@ -16,11 +14,8 @@
      *
      * @param os
      *            target output stream.
-     *
-     * @deprecated Use {@link ASN1OutputStream#create(OutputStream, String)} with
-     *             {@link ASN1Encoding#BER} instead.
      */
-    public BEROutputStream(OutputStream os)
+    BEROutputStream(OutputStream os)
     {
         super(os);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ConstructedOctetStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/ConstructedOctetStream.java
index c595a3e..fd26178 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ConstructedOctetStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ConstructedOctetStream.java
@@ -26,21 +26,14 @@
                 return -1;
             }
 
-            ASN1Encodable asn1Obj = _parser.readObject();
-            if (asn1Obj == null)
+            ASN1OctetStringParser next = getNextParser();
+            if (next == null)
             {
                 return -1;
             }
 
-            if (!(asn1Obj instanceof ASN1OctetStringParser))
-            {
-                throw new IOException("unknown object encountered: " + asn1Obj.getClass());
-            }
-
-            ASN1OctetStringParser s = (ASN1OctetStringParser)asn1Obj;
-
             _first = false;
-            _currentStream = s.getOctetStream();
+            _currentStream = next.getOctetStream();
         }
 
         int totalRead = 0;
@@ -60,22 +53,14 @@
             }
             else
             {
-                ASN1Encodable asn1Obj = _parser.readObject();
-
-                if (asn1Obj == null)
+                ASN1OctetStringParser next = getNextParser();
+                if (next == null)
                 {
                     _currentStream = null;
                     return totalRead < 1 ? -1 : totalRead;
                 }
 
-                if (!(asn1Obj instanceof ASN1OctetStringParser))
-                {
-                    throw new IOException("unknown object encountered: " + asn1Obj.getClass());
-                }
-
-                ASN1OctetStringParser aos = (ASN1OctetStringParser)asn1Obj;
-
-                _currentStream = aos.getOctetStream();
+                _currentStream = next.getOctetStream();
             }
         }
     }
@@ -90,22 +75,14 @@
                 return -1;
             }
 
-            ASN1Encodable asn1Obj = _parser.readObject();
-    
-            if (asn1Obj == null)
+            ASN1OctetStringParser next = getNextParser();
+            if (next == null)
             {
                 return -1;
             }
 
-            if (!(asn1Obj instanceof ASN1OctetStringParser))
-            {
-                throw new IOException("unknown object encountered: " + asn1Obj.getClass());
-            }
-
-            ASN1OctetStringParser s = (ASN1OctetStringParser)asn1Obj;
-
             _first = false;
-            _currentStream = s.getOctetStream();
+            _currentStream = next.getOctetStream();
         }
 
         for (;;)
@@ -117,22 +94,30 @@
                 return b;
             }
 
-            ASN1Encodable asn1Obj = _parser.readObject();
-
-            if (asn1Obj == null)
+            ASN1OctetStringParser next = getNextParser();
+            if (next == null)
             {
                 _currentStream = null;
                 return -1;
             }
 
-            if (!(asn1Obj instanceof ASN1OctetStringParser))
-            {
-                throw new IOException("unknown object encountered: " + asn1Obj.getClass());
-            }
-
-            ASN1OctetStringParser s = (ASN1OctetStringParser)asn1Obj;
-
-            _currentStream = s.getOctetStream();
+            _currentStream = next.getOctetStream();
         }
     }
+
+    private ASN1OctetStringParser getNextParser() throws IOException
+    {
+        ASN1Encodable asn1Obj = _parser.readObject();
+        if (asn1Obj == null)
+        {
+            return null;
+        }
+
+        if (asn1Obj instanceof ASN1OctetStringParser)
+        {
+            return (ASN1OctetStringParser)asn1Obj;
+        }
+
+        throw new IOException("unknown object encountered: " + asn1Obj.getClass());
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DERBMPString.java b/bcprov/src/main/java/org/bouncycastle/asn1/DERBMPString.java
index 2ef4b78..8daf842 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DERBMPString.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DERBMPString.java
@@ -81,9 +81,21 @@
     DERBMPString(
         byte[]   string)
     {
-        char[]  cs = new char[string.length / 2];
+        if (string == null)
+        {
+            throw new NullPointerException("'string' cannot be null");
+        }
 
-        for (int i = 0; i != cs.length; i++)
+        int byteLen = string.length;
+        if (0 != (byteLen & 1))
+        {
+            throw new IllegalArgumentException("malformed BMPString encoding encountered");
+        }
+
+        int charLen = byteLen / 2;
+        char[] cs = new char[charLen];
+
+        for (int i = 0; i != charLen; i++)
         {
             cs[i] = (char)((string[2 * i] << 8) | (string[2 * i + 1] & 0xff));
         }
@@ -93,6 +105,11 @@
 
     DERBMPString(char[] string)
     {
+        if (string == null)
+        {
+            throw new NullPointerException("'string' cannot be null");
+        }
+
         this.string = string;
     }
 
@@ -103,6 +120,11 @@
     public DERBMPString(
         String   string)
     {
+        if (string == null)
+        {
+            throw new NullPointerException("'string' cannot be null");
+        }
+
         this.string = string.toCharArray();
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DERNull.java b/bcprov/src/main/java/org/bouncycastle/asn1/DERNull.java
index c8b58bc..38960a4 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DERNull.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DERNull.java
@@ -14,10 +14,7 @@
 
     private static final byte[]  zeroBytes = new byte[0];
 
-    /**
-     * @deprecated use DERNull.INSTANCE
-     */
-    public DERNull()
+    private DERNull()
     {
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DEROutputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/DEROutputStream.java
index eb07601..c8ac185 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DEROutputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DEROutputStream.java
@@ -5,17 +5,11 @@
 
 /**
  * Stream that outputs encoding based on distinguished encoding rules.
- * 
- * @deprecated Will be removed from public API.
  */
-public class DEROutputStream
+class DEROutputStream
     extends ASN1OutputStream
 {
-    /**
-     * @deprecated Use {@link ASN1OutputStream#create(OutputStream, String)} with
-     *             {@link ASN1Encoding#DER} instead.
-     */
-    public DEROutputStream(OutputStream os)
+    DEROutputStream(OutputStream os)
     {
         super(os);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DERSequence.java b/bcprov/src/main/java/org/bouncycastle/asn1/DERSequence.java
index 0905210..e547471 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DERSequence.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DERSequence.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.asn1;
 
 import java.io.IOException;
-import java.io.OutputStream;
 
 /**
  * Definite length SEQUENCE, encoding tells explicit number of bytes
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DERSet.java b/bcprov/src/main/java/org/bouncycastle/asn1/DERSet.java
index 3591cb4..bc55bcb 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DERSet.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DERSet.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.asn1;
 
 import java.io.IOException;
-import java.io.OutputStream;
 
 /**
  * A DER encoded SET object
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DLExternal.java b/bcprov/src/main/java/org/bouncycastle/asn1/DLExternal.java
index 7508a0a..833c60e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DLExternal.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DLExternal.java
@@ -82,7 +82,7 @@
         {
             baos.write(dataValueDescriptor.getEncoded(ASN1Encoding.DL));
         }
-        DERTaggedObject obj = new DERTaggedObject(true, encoding, externalContent);
+        ASN1TaggedObject obj = new DLTaggedObject(true, encoding, externalContent);
         baos.write(obj.getEncoded(ASN1Encoding.DL));
         
         out.writeEncoded(withTag, BERTags.CONSTRUCTED, BERTags.EXTERNAL, baos.toByteArray());
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DLOutputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/DLOutputStream.java
index 1453379..9c2a88c 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DLOutputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DLOutputStream.java
@@ -5,17 +5,11 @@
 
 /**
  * Stream that outputs encoding based on definite length.
- * 
- * @deprecated Will be removed from public API.
  */
-public class DLOutputStream
+class DLOutputStream
     extends ASN1OutputStream
 {
-    /**
-     * @deprecated Use {@link ASN1OutputStream#create(OutputStream, String)} with
-     *             {@link ASN1Encoding#DL} instead.
-     */
-    public DLOutputStream(OutputStream os)
+    DLOutputStream(OutputStream os)
     {
         super(os);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DLSequenceParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/DLSequenceParser.java
index 06163ef..290a46e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DLSequenceParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DLSequenceParser.java
@@ -1,12 +1,60 @@
 package org.bouncycastle.asn1;
 
+import java.io.IOException;
+
 /**
  * Parser class for DL SEQUENCEs.
+ *
+ * TODO The class is only publicly visible to support 'instanceof' checks; provide an alternative
  */
-public class DLSequenceParser extends DERSequenceParser
+public class DLSequenceParser
+    implements ASN1SequenceParser
 {
+    private ASN1StreamParser _parser;
+
     DLSequenceParser(ASN1StreamParser parser)
     {
-        super(parser);
+        this._parser = parser;
+    }
+
+    /**
+     * Return the next object in the SEQUENCE.
+     *
+     * @return next object in SEQUENCE.
+     * @throws IOException if there is an issue loading the object.
+     */
+    public ASN1Encodable readObject()
+        throws IOException
+    {
+        return _parser.readObject();
+    }
+
+    /**
+     * Return an in memory, encodable, representation of the SEQUENCE.
+     *
+     * @return a DLSequence.
+     * @throws IOException if there is an issue loading the data.
+     */
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+         return new DLSequence(_parser.readVector());
+    }
+
+    /**
+     * Return a DLSequence representing this parser and its contents.
+     *
+     * @return a DLSequence.
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        try
+        {
+            return getLoadedObject();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException(e.getMessage());
+        }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DLSetParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/DLSetParser.java
index e6ca027..9f4421d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DLSetParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DLSetParser.java
@@ -1,12 +1,60 @@
 package org.bouncycastle.asn1;
 
+import java.io.IOException;
+
 /**
  * Parser class for DL SETs.
+ *
+ * TODO The class is only publicly visible to support 'instanceof' checks; provide an alternative
  */
-public class DLSetParser extends DERSetParser
+public class DLSetParser
+    implements ASN1SetParser
 {
+    private ASN1StreamParser _parser;
+
     DLSetParser(ASN1StreamParser parser)
     {
-        super(parser);
+        this._parser = parser;
+    }
+
+    /**
+     * Return the next object in the SET.
+     *
+     * @return next object in SET.
+     * @throws IOException if there is an issue loading the object.
+     */
+    public ASN1Encodable readObject()
+        throws IOException
+    {
+        return _parser.readObject();
+    }
+
+    /**
+     * Return an in memory, encodable, representation of the SET.
+     *
+     * @return a DLSet.
+     * @throws IOException if there is an issue loading the data.
+     */
+    public ASN1Primitive getLoadedObject()
+        throws IOException
+    {
+        return new DLSet(_parser.readVector());
+    }
+
+    /**
+     * Return a DLSet representing this parser and its contents.
+     *
+     * @return a DLSet
+     */
+    public ASN1Primitive toASN1Primitive()
+    {
+        try
+        {
+            return getLoadedObject();
+        }
+        catch (IOException e)
+        {
+            throw new ASN1ParsingException(e.getMessage(), e);
+        }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/DefiniteLengthInputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/DefiniteLengthInputStream.java
index dd533ca..359bcc2 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/DefiniteLengthInputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/DefiniteLengthInputStream.java
@@ -23,7 +23,7 @@
         int         length,
         int         limit)
     {
-        super(in, limit, length);
+        super(in, limit);
 
         if (length < 0)
         {
@@ -91,6 +91,33 @@
         return numRead;
     }
 
+    void readAllIntoByteArray(byte[] buf)
+        throws IOException
+    {
+        if (_remaining != buf.length)
+        {
+            throw new IllegalArgumentException("buffer length not right for data");
+        }
+
+        if (_remaining == 0)
+        {
+            return;
+        }
+
+        // make sure it's safe to do this!
+        int limit = getLimit();
+        if (_remaining >= limit)
+        {
+            throw new IOException("corrupted stream - out of bounds length found: " + _remaining + " >= " + limit);
+        }
+
+        if ((_remaining -= Streams.readFully(_in, buf)) != 0)
+        {
+            throw new EOFException("DEF length " + _originalLength + " object truncated by " + _remaining);
+        }
+        setParentEofDetect(true);
+    }
+
     byte[] toByteArray()
         throws IOException
     {
@@ -100,9 +127,10 @@
         }
 
         // make sure it's safe to do this!
-        if (_remaining >= this.getLimit())
+        int limit = getLimit();
+        if (_remaining >= limit)
         {
-            throw new IOException("corrupted stream - out of bounds length found: " + _remaining + " >= " + this.getLimit());
+            throw new IOException("corrupted stream - out of bounds length found: " + _remaining + " >= " + limit);
         }
 
         byte[] bytes = new byte[_remaining];
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/IndefiniteLengthInputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
index e37af64..353da3b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/IndefiniteLengthInputStream.java
@@ -17,7 +17,7 @@
         int         limit)
         throws IOException
     {
-        super(in, limit, limit);
+        super(in, limit);
 
         _b1 = in.read();
         _b2 = in.read();
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/LazyConstructionEnumeration.java b/bcprov/src/main/java/org/bouncycastle/asn1/LazyConstructionEnumeration.java
index 31d988d..c671e5b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/LazyConstructionEnumeration.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/LazyConstructionEnumeration.java
@@ -2,6 +2,7 @@
 
 import java.io.IOException;
 import java.util.Enumeration;
+import java.util.NoSuchElementException;
 
 class LazyConstructionEnumeration
     implements Enumeration
@@ -22,11 +23,13 @@
 
     public Object nextElement()
     {
-        Object o = nextObj;
-
-        nextObj = readObject();
-
-        return o;
+        if (nextObj != null)
+        {
+            Object o = nextObj;
+            nextObj = readObject();
+            return o;
+        }
+        throw new NoSuchElementException();
     }
 
     private Object readObject()
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/LimitedInputStream.java b/bcprov/src/main/java/org/bouncycastle/asn1/LimitedInputStream.java
index 410a3e4..b18a65b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/LimitedInputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/LimitedInputStream.java
@@ -10,16 +10,13 @@
 {
     protected final InputStream _in;
     private int _limit;
-    private int _length;
 
     LimitedInputStream(
         InputStream in,
-        int         limit,
-        int         length)
+        int         limit)
     {
         this._in = in;
         this._limit = limit;
-        this._length = length;
     }
 
     int getLimit()
@@ -27,12 +24,6 @@
         return _limit;
     }
 
-    int getRemaining()
-    {
-        // TODO: maybe one day this can become more accurate
-        return _length;
-    }
-    
     protected void setParentEofDetect(boolean on)
     {
         if (_in instanceof IndefiniteLengthInputStream)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertOrEncCert.java b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertOrEncCert.java
index b94a79c..fdc4124 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertOrEncCert.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertOrEncCert.java
@@ -5,6 +5,7 @@
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 
 public class CertOrEncCert
@@ -12,7 +13,7 @@
     implements ASN1Choice
 {
     private CMPCertificate certificate;
-    private EncryptedValue encryptedCert;
+    private EncryptedKey encryptedKey;
 
     private CertOrEncCert(ASN1TaggedObject tagged)
     {
@@ -22,7 +23,7 @@
         }
         else if (tagged.getTagNo() == 1)
         {
-            encryptedCert = EncryptedValue.getInstance(tagged.getObject());
+            encryptedKey = EncryptedKey.getInstance(tagged.getObject());
         }
         else
         {
@@ -62,7 +63,17 @@
             throw new IllegalArgumentException("'encryptedCert' cannot be null");
         }
 
-        this.encryptedCert = encryptedCert;
+        this.encryptedKey = new EncryptedKey(encryptedCert);
+    }
+
+    public CertOrEncCert(EncryptedKey encryptedKey)
+    {
+        if (encryptedKey == null)
+        {
+            throw new IllegalArgumentException("'encryptedKey' cannot be null");
+        }
+
+        this.encryptedKey = encryptedKey;
     }
 
     public CMPCertificate getCertificate()
@@ -70,16 +81,16 @@
         return certificate;
     }
 
-    public EncryptedValue getEncryptedCert()
+    public EncryptedKey getEncryptedCert()
     {
-        return encryptedCert;
+        return encryptedKey;
     }
 
     /**
      * <pre>
      * CertOrEncCert ::= CHOICE {
      *                      certificate     [0] CMPCertificate,
-     *                      encryptedCert   [1] EncryptedValue
+     *                      encryptedCert   [1] EncryptedKey
      *           }
      * </pre>
      * @return a basic ASN.1 object representation.
@@ -91,6 +102,6 @@
             return new DERTaggedObject(true, 0, certificate);
         }
 
-        return new DERTaggedObject(true, 1, encryptedCert);
+        return new DERTaggedObject(true, 1, encryptedKey);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
index dff93c1..0706d61 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/CertifiedKeyPair.java
@@ -7,6 +7,7 @@
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 import org.bouncycastle.asn1.crmf.PKIPublicationInfo;
 
@@ -14,7 +15,7 @@
  * <pre>
  * CertifiedKeyPair ::= SEQUENCE {
  *                                  certOrEncCert       CertOrEncCert,
- *                                  privateKey      [0] EncryptedValue      OPTIONAL,
+ *                                  privateKey      [0] EncryptedKey      OPTIONAL,
  *                                  -- see [CRMF] for comment on encoding
  *                                  publicationInfo [1] PKIPublicationInfo  OPTIONAL
  *       }
@@ -24,7 +25,7 @@
     extends ASN1Object
 {
     private CertOrEncCert certOrEncCert;
-    private EncryptedValue privateKey;
+    private EncryptedKey privateKey;
     private PKIPublicationInfo  publicationInfo;
 
     private CertifiedKeyPair(ASN1Sequence seq)
@@ -38,7 +39,7 @@
                 ASN1TaggedObject tagged = ASN1TaggedObject.getInstance(seq.getObjectAt(1));
                 if (tagged.getTagNo() == 0)
                 {
-                    privateKey = EncryptedValue.getInstance(tagged.getObject());
+                    privateKey = EncryptedKey.getInstance(tagged.getObject());
                 }
                 else
                 {
@@ -47,7 +48,7 @@
             }
             else
             {
-                privateKey = EncryptedValue.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(1)).getObject());
+                privateKey = EncryptedKey.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(1)).getObject());
                 publicationInfo = PKIPublicationInfo.getInstance(ASN1TaggedObject.getInstance(seq.getObjectAt(2)).getObject());
             }
         }
@@ -71,7 +72,22 @@
     public CertifiedKeyPair(
         CertOrEncCert certOrEncCert)
     {
-        this(certOrEncCert, null, null);
+        this(certOrEncCert, (EncryptedKey)null, null);
+    }
+
+    public CertifiedKeyPair(
+        CertOrEncCert certOrEncCert,
+        EncryptedKey privateKey,
+        PKIPublicationInfo  publicationInfo)
+    {
+        if (certOrEncCert == null)
+        {
+            throw new IllegalArgumentException("'certOrEncCert' cannot be null");
+        }
+
+        this.certOrEncCert = certOrEncCert;
+        this.privateKey = privateKey;
+        this.publicationInfo = publicationInfo;
     }
 
     public CertifiedKeyPair(
@@ -85,7 +101,7 @@
         }
 
         this.certOrEncCert = certOrEncCert;
-        this.privateKey = privateKey;
+        this.privateKey = (privateKey != null) ? new EncryptedKey(privateKey) : (EncryptedKey)null;
         this.publicationInfo = publicationInfo;
     }
 
@@ -94,7 +110,7 @@
         return certOrEncCert;
     }
 
-    public EncryptedValue getPrivateKey()
+    public EncryptedKey getPrivateKey()
     {
         return privateKey;
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java
index 36a833b..2b1b52c 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cmp/PKIHeaderBuilder.java
@@ -76,7 +76,7 @@
         return setRecipKID(kid == null ? null : new DEROctetString(kid));
     }
 
-    public PKIHeaderBuilder setRecipKID(DEROctetString kid)
+    public PKIHeaderBuilder setRecipKID(ASN1OctetString kid)
     {
         recipKID = kid;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attribute.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attribute.java
index 625c1cb..f5995b0 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attribute.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attribute.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#page-14">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#page-14">RFC 5652</a>:
  * Attribute is a pair of OID (as type identifier) + set of values.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attributes.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attributes.java
index 4ffe5ea..420372a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attributes.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Attributes.java
@@ -8,7 +8,7 @@
 import org.bouncycastle.asn1.DLSet;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652">RFC 5652</a> defines
+ * <a href="https://tools.ietf.org/html/rfc5652">RFC 5652</a> defines
  * 5 "SET OF Attribute" entities with 5 different names.
  * This is common implementation for them all:
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthEnvelopedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
index d1994c8..6e0a85d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthEnvelopedData.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5083">RFC 5083</a>:
+ * <a href="https://tools.ietf.org/html/rfc5083">RFC 5083</a>:
  *
  * CMS AuthEnveloped Data object.
  * <p>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthenticatedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthenticatedData.java
index 271cd8e..4577d81 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthenticatedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/AuthenticatedData.java
@@ -15,7 +15,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-9.1">RFC 5652</a> section 9.1:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-9.1">RFC 5652</a> section 9.1:
  * The AuthenticatedData carries AuthAttributes and other data
  * which define what really is being signed.
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CCMParameters.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CCMParameters.java
index 9a15eb8..b281d76 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CCMParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CCMParameters.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.util.Arrays;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5084">RFC 5084</a>: CCMParameters object.
+ * <a href="https://tools.ietf.org/html/rfc5084">RFC 5084</a>: CCMParameters object.
  * <p>
  * <pre>
  CCMParameters ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CMSAttributes.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CMSAttributes.java
index d452ef8..0801078 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CMSAttributes.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CMSAttributes.java
@@ -5,8 +5,8 @@
 
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652">RFC 5652</a> CMS attribute OID constants.
- * and <a href="http://tools.ietf.org/html/rfc6211">RFC 6211</a> Algorithm Identifier Protection Attribute.
+ * <a href="https://tools.ietf.org/html/rfc5652">RFC 5652</a> CMS attribute OID constants.
+ * and <a href="https://tools.ietf.org/html/rfc6211">RFC 6211</a> Algorithm Identifier Protection Attribute.
  * <pre>
  * contentType      ::= 1.2.840.113549.1.9.3
  * messageDigest    ::= 1.2.840.113549.1.9.4
@@ -28,7 +28,7 @@
     ASN1ObjectIdentifier  signingTime = PKCSObjectIdentifiers.pkcs_9_at_signingTime;
     /** PKCS#9: 1.2.840.113549.1.9.6 */
     ASN1ObjectIdentifier  counterSignature = PKCSObjectIdentifiers.pkcs_9_at_counterSignature;
-    /** PKCS#9: 1.2.840.113549.1.9.16.6.2.4 - See <a href="http://tools.ietf.org/html/rfc2634">RFC 2634</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.6.2.4 - See <a href="https://tools.ietf.org/html/rfc2634">RFC 2634</a> */
     ASN1ObjectIdentifier  contentHint = PKCSObjectIdentifiers.id_aa_contentHint;
 
     ASN1ObjectIdentifier  cmsAlgorithmProtect = PKCSObjectIdentifiers.id_aa_cmsAlgorithmProtect;
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedData.java
index 85b3251..e62afda 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedData.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /** 
- * <a href="http://tools.ietf.org/html/rfc3274">RFC 3274</a>: CMS Compressed Data.
+ * <a href="https://tools.ietf.org/html/rfc3274">RFC 3274</a>: CMS Compressed Data.
  * 
  * <pre>
  * CompressedData ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedDataParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedDataParser.java
index 41895ce..4fedf67 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedDataParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/CompressedDataParser.java
@@ -7,7 +7,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * Parser of <a href="http://tools.ietf.org/html/rfc3274">RFC 3274</a> {@link CompressedData} object.
+ * Parser of <a href="https://tools.ietf.org/html/rfc3274">RFC 3274</a> {@link CompressedData} object.
  * <p>
  * <pre>
  * CompressedData ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfo.java
index d81b5c1..be197d2 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfo.java
@@ -11,8 +11,8 @@
 import org.bouncycastle.asn1.BERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-3">RFC 5652</a> ContentInfo, and 
- * <a href="http://tools.ietf.org/html/rfc5652#section-5.2">RFC 5652</a> EncapsulatedContentInfo objects.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-3">RFC 5652</a> ContentInfo, and
+ * <a href="https://tools.ietf.org/html/rfc5652#section-5.2">RFC 5652</a> EncapsulatedContentInfo objects.
  *
  * <pre>
  * ContentInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfoParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfoParser.java
index 19f0ec8..1aea467 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfoParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ContentInfoParser.java
@@ -8,7 +8,7 @@
 import org.bouncycastle.asn1.ASN1TaggedObjectParser;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-3">RFC 5652</a> {@link ContentInfo} object parser.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-3">RFC 5652</a> {@link ContentInfo} object parser.
  *
  * <pre>
  * ContentInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/DigestedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/DigestedData.java
index 8bb0ed0..ea9bd2f 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/DigestedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/DigestedData.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /** 
- * <a href="http://tools.ietf.org/html/rfc5652#section-7">RFC 5652</a> DigestedData object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-7">RFC 5652</a> DigestedData object.
  * <pre>
  * DigestedData ::= SEQUENCE {
  *       version CMSVersion,
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
index adf860a..1eaa9a7 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfo.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EncryptedContentInfo object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EncryptedContentInfo object.
  *
  * <pre>
  * EncryptedContentInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
index 77fb0bb..36702f3 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedContentInfoParser.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * Parser for <a href="http://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EncryptedContentInfo object.
+ * Parser for <a href="https://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EncryptedContentInfo object.
  * <p>
  * <pre>
  * EncryptedContentInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedData.java
index af4d302..a186974 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EncryptedData.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.BERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-8">RFC 5652</a> EncryptedData object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-8">RFC 5652</a> EncryptedData object.
  * <p>
  * <pre>
  * EncryptedData ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedData.java
index 75d37fd..02d5367 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedData.java
@@ -13,7 +13,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EnvelopedData object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> EnvelopedData object.
  * <pre>
  * EnvelopedData ::= SEQUENCE {
  *     version CMSVersion,
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedDataParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
index 774813a..163c8db 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/EnvelopedDataParser.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.BERTags;
 
 /** 
- * Parser of <a href="http://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> {@link EnvelopedData} object.
+ * Parser of <a href="https://tools.ietf.org/html/rfc5652#section-6.1">RFC 5652</a> {@link EnvelopedData} object.
  * <p>
  * <pre>
  * EnvelopedData ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Evidence.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Evidence.java
index d91327c..48c02d4 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Evidence.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Evidence.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.tsp.EvidenceRecord;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>:
+ * <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>:
  * Binding Documents with Time-Stamps; Evidence object.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/GCMParameters.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/GCMParameters.java
index 0ba1511..76a98dc 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/GCMParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/GCMParameters.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.util.Arrays;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5084">RFC 5084</a>: GCMParameters object.
+ * <a href="https://tools.ietf.org/html/rfc5084">RFC 5084</a>: GCMParameters object.
  * <p>
  * <pre>
  GCMParameters ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
index e884071..960ee7a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/IssuerAndSerialNumber.java
@@ -14,7 +14,7 @@
 import org.bouncycastle.asn1.x509.X509Name;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-10.2.4">RFC 5652</a>: IssuerAndSerialNumber object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-10.2.4">RFC 5652</a>: IssuerAndSerialNumber object.
  * <p>
  * <pre>
  * IssuerAndSerialNumber ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKIdentifier.java
index 920e68b..97fae7f 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKIdentifier.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.3">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.3">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
index 4ff4224..75802f8 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KEKRecipientInfo.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.3">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.3">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
index 6580cd4..f67d3b8 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientIdentifier.java
@@ -8,7 +8,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
index f14c204..8be2537 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyAgreeRecipientInfo.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
index 59a5458..442e891 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/KeyTransRecipientInfo.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * KeyTransRecipientInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/MetaData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/MetaData.java
index a9209fb..709a2d0 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/MetaData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/MetaData.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERUTF8String;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>:
+ * <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>:
  * Binding Documents with Time-Stamps; MetaData object.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
index 3fcf288..0dbeb4b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorIdentifierOrKey.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * OriginatorIdentifierOrKey ::= CHOICE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorInfo.java
index 5713c47..265893b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorInfo.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>: OriginatorInfo object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>: OriginatorInfo object.
  * <pre>
  * RFC 3369:
  *
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorPublicKey.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
index 07a6e37..8a1ae1b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OriginatorPublicKey.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherKeyAttribute.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
index 7a25c12..c141a54 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherKeyAttribute.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-10.2.7">RFC 5652</a>: OtherKeyAttribute object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-10.2.7">RFC 5652</a>: OtherKeyAttribute object.
  * <p>
  * <pre>
  * OtherKeyAttribute ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
index ebffc7d..0685c82 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRecipientInfo.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.5">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.5">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * OtherRecipientInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java
index b50f255..5ad18bf 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/OtherRevocationInfoFormat.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-10.2.1">RFC 5652</a>: OtherRevocationInfoFormat object.
+ * <a href="https://tools.ietf.org/html/rfc5652#section-10.2.1">RFC 5652</a>: OtherRevocationInfoFormat object.
  * <p>
  * <pre>
  * OtherRevocationInfoFormat ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
index 326875b..49d9c13 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/PasswordRecipientInfo.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-10.2.7">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-10.2.7">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * PasswordRecipientInfo ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
index 0d65d8a..4ee16da 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientEncryptedKey.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * RecipientEncryptedKey ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientIdentifier.java
index 66b154a..5a03d4c 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientIdentifier.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.1">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <pre>
  * RecipientIdentifier ::= CHOICE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientInfo.java
index 39a7bb2..01ea91a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientInfo.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
index 721008b..d0d8018 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/RecipientKeyIdentifier.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-6.2.2">RFC 5652</a>:
  * Content encryption key delivery mechanisms.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SCVPReqRes.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SCVPReqRes.java
index 5d1a135..b1e4a37 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SCVPReqRes.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SCVPReqRes.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5940">RFC 5940</a>:
+ * <a href="https://tools.ietf.org/html/rfc5940">RFC 5940</a>:
  * Additional Cryptographic Message Syntax (CMS) Revocation Information Choices.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedData.java
index d92cc80..a30b764 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedData.java
@@ -16,7 +16,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-5.1">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-5.1">RFC 5652</a>:
  * <p>
  * A signed data object containing multitude of {@link SignerInfo}s.
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedDataParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedDataParser.java
index df22b8e..76bf6c3 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedDataParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignedDataParser.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.BERTags;
 
 /**
- * Parser for <a href="http://tools.ietf.org/html/rfc5652#section-5.1">RFC 5652</a>: {@link SignedData} object.
+ * Parser for <a href="https://tools.ietf.org/html/rfc5652#section-5.1">RFC 5652</a>: {@link SignedData} object.
  * <p>
  * <pre>
  * SignedData ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerIdentifier.java
index 2543eb1..d568148 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerIdentifier.java
@@ -9,7 +9,7 @@
 import org.bouncycastle.asn1.DERTaggedObject;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-5.3">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-5.3">RFC 5652</a>:
  * Identify who signed the containing {@link SignerInfo} object.
  * <p>
  * The certificates referred to by this are at containing {@link SignedData} structure.
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerInfo.java
index 084f5bc..d9640de 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/SignerInfo.java
@@ -16,7 +16,7 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-5.3">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-5.3">RFC 5652</a>:
  * Signature container per Signer, see {@link SignerIdentifier}.
  * <pre>
  * PKCS#7:
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Time.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Time.java
index 84f12a9..8890a82 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/Time.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/Time.java
@@ -16,7 +16,7 @@
 import org.bouncycastle.asn1.DERUTCTime;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5652#section-11.3">RFC 5652</a>:
+ * <a href="https://tools.ietf.org/html/rfc5652#section-11.3">RFC 5652</a>:
  * Dual-mode timestamp format producing either UTCTIme or GeneralizedTime.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampAndCRL.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampAndCRL.java
index 16337be..dfa97a9 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampAndCRL.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampAndCRL.java
@@ -8,7 +8,7 @@
 import org.bouncycastle.asn1.x509.CertificateList;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>
+ * <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>
  * Binding Documents with Time-Stamps; TimeStampAndCRL object.
  * <pre>
  * TimeStampAndCRL ::= SEQUENCE {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java
index c3a8ea2..8bc4c6e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampTokenEvidence.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERSequence;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>
+ * <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>
  * Binding Documents with Time-Stamps; TimeStampTokenEvidence object.
  * <pre>
  * TimeStampTokenEvidence ::=
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedData.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedData.java
index d3f737e..9296463 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedData.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedData.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERIA5String;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>:
+ * <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>:
  * Binding Documents with Time-Stamps; TimeStampedData object.
  * <p>
  * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedDataParser.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedDataParser.java
index 07ede56..5fef357 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedDataParser.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/TimeStampedDataParser.java
@@ -10,7 +10,7 @@
 import org.bouncycastle.asn1.DERIA5String;
 
 /**
- * Parser for <a href="http://tools.ietf.org/html/rfc5544">RFC 5544</a>:
+ * Parser for <a href="https://tools.ietf.org/html/rfc5544">RFC 5544</a>:
  * {@link TimeStampedData} object.
  * <p>
  * <pre>
@@ -71,6 +71,11 @@
         return null;
     }
 
+    public int getVersion()
+    {
+        return version.getValue().intValue();
+    }
+    
     public DERIA5String getDataUri()
     {
         return dataUri;
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
index 59234b5..f2e886d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cms/ecc/MQVuserKeyingMaterial.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.cms.OriginatorPublicKey;
 
 /**
- * <a href="http://tools.ietf.org/html/rfc5753">RFC 5753/3278</a>: MQVuserKeyingMaterial object.
+ * <a href="https://tools.ietf.org/html/rfc5753">RFC 5753/3278</a>: MQVuserKeyingMaterial object.
  * <pre>
  * MQVuserKeyingMaterial ::= SEQUENCE {
  *   ephemeralPublicKey OriginatorPublicKey,
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java b/bcprov/src/main/java/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
index 8165c13..f5b7e74 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/cryptopro/ECGOST3410NamedCurves.java
@@ -6,6 +6,8 @@
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
@@ -222,13 +224,21 @@
      * isn't present.
      *
      * @param oid an object identifier representing a named parameters, if present.
+     * 
+     * @deprecated Use {@link #getByOIDX9(ASN1ObjectIdentifier)} instead.
      */
-    public static ECDomainParameters getByOID(
-        ASN1ObjectIdentifier  oid)
+    public static ECDomainParameters getByOID(ASN1ObjectIdentifier oid)
     {
         return (ECDomainParameters)params.get(oid);
     }
 
+    public static X9ECParameters getByOIDX9(ASN1ObjectIdentifier oid)
+    {
+        ECDomainParameters ec = (ECDomainParameters)params.get(oid);
+        return ec == null ? null : new X9ECParameters(ec.getCurve(), new X9ECPoint(ec.getG(), false), ec.getN(),
+            ec.getH(), ec.getSeed());
+    }
+
     /**
      * returns an enumeration containing the name strings for parameters
      * contained in this structure.
@@ -238,17 +248,19 @@
         return names.elements();
     }
 
-    public static ECDomainParameters getByName(
-        String  name)
+    /**
+     * @deprecated Use {@link #getByNameX9(String)} instead.
+     */
+    public static ECDomainParameters getByName(String name)
     {
         ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(name);
+        return oid == null ? null : (ECDomainParameters)params.get(oid);
+    }
 
-        if (oid != null)
-        {
-            return (ECDomainParameters)params.get(oid);
-        }
-
-        return null;
+    public static X9ECParameters getByNameX9(String name)
+    {
+        ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)objIds.get(name);
+        return oid == null ? null : getByOIDX9(oid);
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java
index d5f6ab6..56ff7ce 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/dvcs/DVCSObjectIdentifiers.java
@@ -3,7 +3,7 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
- * OIDs for <a href="http://tools.ietf.org/html/rfc3029">RFC 3029</a>
+ * OIDs for <a href="https://tools.ietf.org/html/rfc3029">RFC 3029</a>
  * Data Validation and Certification Server Protocols
  */
 public interface DVCSObjectIdentifiers
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
index 805a506..35e65f5 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/eac/EACObjectIdentifiers.java
@@ -5,7 +5,7 @@
 /**
  * German Federal Office for Information Security
  * (Bundesamt f&uuml;r Sicherheit in der Informationstechnik)
- * <a href="http://www.bsi.bund.de/">http://www.bsi.bund.de/</a>
+ * <a href="https://www.bsi.bund.de/">https://www.bsi.bund.de/</a>
  * <p>
  * <a href="https://www.bsi.bund.de/EN/Publications/TechnicalGuidelines/TR03110/BSITR03110.html">BSI TR-03110</a>
  * Technical Guideline Advanced Security Mechanisms for Machine Readable Travel Documents
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/isara/IsaraObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/isara/IsaraObjectIdentifiers.java
new file mode 100644
index 0000000..cf46920
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/isara/IsaraObjectIdentifiers.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.asn1.isara;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+
+public interface IsaraObjectIdentifiers
+{
+    /*
+    id-alg-xmss  OBJECT IDENTIFIER ::= { itu-t(0)
+             identified-organization(4) etsi(0) reserved(127)
+             etsi-identified-organization(0) isara(15) algorithms(1)
+             asymmetric(1) xmss(13) 0 }
+     */
+    static ASN1ObjectIdentifier id_alg_xmss = new ASN1ObjectIdentifier("0.4.0.127.0.15.1.1.13.0");
+
+    /*
+      id-alg-xmssmt  OBJECT IDENTIFIER ::= { itu-t(0)
+         identified-organization(4) etsi(0) reserved(127)
+         etsi-identified-organization(0) isara(15) algorithms(1)
+         asymmetric(1) xmssmt(14) 0 }
+     */
+    static ASN1ObjectIdentifier id_alg_xmssmt = new ASN1ObjectIdentifier("0.4.0.127.0.15.1.1.14.0");
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java b/bcprov/src/main/java/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
index 538a2d0..2aed65f 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/isismtt/x509/AdmissionSyntax.java
@@ -67,8 +67,8 @@
  * component namingAuthorityId are grouped under the OID-branch
  * id-isis-at-namingAuthorities and must be applied for.
  * <li>See
- * http://www.teletrust.de/anwend.asp?Id=30200&amp;Sprache=E_&amp;HomePG=0 for
- * an application form and http://www.teletrust.de/links.asp?id=30220,11
+ * https://www.teletrust.de/anwend.asp?Id=30200&amp;Sprache=E_&amp;HomePG=0 for
+ * an application form and https://www.teletrust.de/links.asp?id=30220,11
  * for an overview of registered naming authorities.
  * <li> By means of the data type ProfessionInfo certain professions,
  * specializations, disciplines, fields of activity, etc. are identified. A
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
index 73575f1..a461133 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/kisa/KISAObjectIdentifiers.java
@@ -6,10 +6,10 @@
  * Korea Information Security Agency (KISA)
  * ({iso(1) member-body(2) kr(410) kisa(200004)})
  * <p>
- * See <a href="http://tools.ietf.org/html/rfc4010">RFC 4010</a>
+ * See <a href="https://tools.ietf.org/html/rfc4010">RFC 4010</a>
  * Use of the SEED Encryption Algorithm
  * in Cryptographic Message Syntax (CMS),
- * and <a href="http://tools.ietf.org/html/rfc4269">RFC 4269</a>
+ * and <a href="https://tools.ietf.org/html/rfc4269">RFC 4269</a>
  * The SEED Encryption Algorithm
  */
 public interface KISAObjectIdentifiers
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
index 0f0b10d..89b91d0 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/misc/MiscObjectIdentifiers.java
@@ -102,6 +102,12 @@
     ASN1ObjectIdentifier cast5CBC = entrust.branch("66.10");
 
     //
+    // HMAC-SHA1       hMAC-SHA1 OBJECT IDENTIFIER ::= { iso(1) identified-organization(3)
+    //       dod(6) internet(1) security(5) mechanisms(5) 8 1 2 }
+    //
+    ASN1ObjectIdentifier hMAC_SHA1 = new ASN1ObjectIdentifier("1.3.6.1.5.5.8.1.2");
+
+    //
     // Ascom
     //
     ASN1ObjectIdentifier as_sys_sec_alg_ideaCBC = new ASN1ObjectIdentifier("1.3.6.1.4.1.188.7.1.1.2");
@@ -135,4 +141,11 @@
     //
     // Scrypt
     ASN1ObjectIdentifier id_scrypt = new ASN1ObjectIdentifier("1.3.6.1.4.1.11591.4.11");
+
+    // Composite key/signature oid - prototyping
+    //
+    //    id-alg-composite OBJECT IDENTIFIER ::= {
+    //        iso(1)  identified-organization(3) dod(6) internet(1) private(4)
+    //        enterprise(1) OpenCA(18227) Algorithms(2) id-alg-composite(1) }
+    ASN1ObjectIdentifier id_alg_composite = new ASN1ObjectIdentifier("1.3.6.1.4.1.18227.2.1");
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE128_params.java b/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE128_params.java
new file mode 100644
index 0000000..129034b
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE128_params.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.asn1.nist;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *   KMACwithSHAKE128-params ::= SEQUENCE {
+ *      kMACOutputLength     INTEGER DEFAULT 256, -- Output length in bits
+ *      customizationString  OCTET STRING DEFAULT ''H
+ *    }
+ * </pre>
+ */
+public class KMACwithSHAKE128_params
+    extends ASN1Object
+{
+    private static final byte[] EMPTY_STRING = new byte[0];
+    private static final int DEF_LENGTH = 256;
+
+    private final int outputLength;
+    private final byte[] customizationString;
+
+    public KMACwithSHAKE128_params(int outputLength)
+    {
+        this.outputLength = outputLength;
+        this.customizationString = EMPTY_STRING;
+    }
+
+    public KMACwithSHAKE128_params(int outputLength, byte[] customizationString)
+    {
+        this.outputLength = outputLength;
+        this.customizationString = Arrays.clone(customizationString);
+    }
+
+    public static KMACwithSHAKE128_params getInstance(Object o)
+    {
+        if (o instanceof KMACwithSHAKE128_params)
+        {
+            return (KMACwithSHAKE128_params)o;
+        }
+        else if (o != null)
+        {
+            return new KMACwithSHAKE128_params(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    private KMACwithSHAKE128_params(ASN1Sequence seq)
+    {
+        if (seq.size() > 2)
+        {
+            throw new IllegalArgumentException("sequence size greater than 2");
+        }
+
+        if (seq.size() == 2)
+        {
+            this.outputLength = ASN1Integer.getInstance(seq.getObjectAt(0)).intValueExact();
+            this.customizationString = Arrays.clone(ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets());
+        }
+        else if (seq.size() == 1)
+        {
+            if (seq.getObjectAt(0) instanceof ASN1Integer)
+            {
+                this.outputLength = ASN1Integer.getInstance(seq.getObjectAt(0)).intValueExact();
+                this.customizationString = EMPTY_STRING;
+            }
+            else
+            {
+                this.outputLength = DEF_LENGTH;
+                this.customizationString = Arrays.clone(ASN1OctetString.getInstance(seq.getObjectAt(0)).getOctets());
+            }
+        }
+        else
+        {
+            this.outputLength = DEF_LENGTH;
+            this.customizationString = EMPTY_STRING;
+        }
+    }
+
+    public int getOutputLength()
+    {
+        return outputLength;
+    }
+
+    public byte[] getCustomizationString()
+    {
+        return Arrays.clone(customizationString);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (outputLength != DEF_LENGTH)
+        {
+            v.add(new ASN1Integer(outputLength));
+        }
+
+        if (customizationString.length != 0)
+        {
+            v.add(new DEROctetString(getCustomizationString()));
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE256_params.java b/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE256_params.java
new file mode 100644
index 0000000..390c903
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/nist/KMACwithSHAKE256_params.java
@@ -0,0 +1,114 @@
+package org.bouncycastle.asn1.nist;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *   KMACwithSHAKE256-params ::= SEQUENCE {
+ *      kMACOutputLength     INTEGER DEFAULT 512, -- Output length in bits
+ *      customizationString  OCTET STRING DEFAULT ''H
+ *    }
+ * </pre>
+ */
+public class KMACwithSHAKE256_params
+    extends ASN1Object
+{
+    private static final byte[] EMPTY_STRING = new byte[0];
+    private static final int DEF_LENGTH = 512;
+
+    private final int outputLength;
+    private final byte[] customizationString;
+
+    public KMACwithSHAKE256_params(int outputLength)
+    {
+        this.outputLength = outputLength;
+        this.customizationString = EMPTY_STRING;
+    }
+
+    public KMACwithSHAKE256_params(int outputLength, byte[] customizationString)
+    {
+        this.outputLength = outputLength;
+        this.customizationString = Arrays.clone(customizationString);
+    }
+
+    public static KMACwithSHAKE256_params getInstance(Object o)
+    {
+        if (o instanceof KMACwithSHAKE256_params)
+        {
+            return (KMACwithSHAKE256_params)o;
+        }
+        else if (o != null)
+        {
+            return new KMACwithSHAKE256_params(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    private KMACwithSHAKE256_params(ASN1Sequence seq)
+    {
+        if (seq.size() > 2)
+        {
+            throw new IllegalArgumentException("sequence size greater than 2");
+        }
+
+        if (seq.size() == 2)
+        {
+            this.outputLength = ASN1Integer.getInstance(seq.getObjectAt(0)).intValueExact();
+            this.customizationString = Arrays.clone(ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets());
+        }
+        else if (seq.size() == 1)
+        {
+            if (seq.getObjectAt(0) instanceof ASN1Integer)
+            {
+                this.outputLength = ASN1Integer.getInstance(seq.getObjectAt(0)).intValueExact();
+                this.customizationString = EMPTY_STRING;
+            }
+            else
+            {
+                this.outputLength = DEF_LENGTH;
+                this.customizationString = Arrays.clone(ASN1OctetString.getInstance(seq.getObjectAt(0)).getOctets());
+            }
+        }
+        else
+        {
+            this.outputLength = DEF_LENGTH;
+            this.customizationString = EMPTY_STRING;
+        }
+    }
+
+    public int getOutputLength()
+    {
+        return outputLength;
+    }
+
+    public byte[] getCustomizationString()
+    {
+        return Arrays.clone(customizationString);
+    }
+    
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        if (outputLength != DEF_LENGTH)
+        {
+            v.add(new ASN1Integer(outputLength));
+        }
+
+        if (customizationString.length != 0)
+        {
+            v.add(new DEROctetString(getCustomizationString()));
+        }
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
index 49c0e6d..8de357a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/nist/NISTObjectIdentifiers.java
@@ -51,6 +51,14 @@
     static final ASN1ObjectIdentifier    id_hmacWithSHA3_384 = hashAlgs.branch("15");
     /** 2.16.840.1.101.3.4.2.16 */
     static final ASN1ObjectIdentifier    id_hmacWithSHA3_512 = hashAlgs.branch("16");
+    /** 2.16.840.1.101.3.4.2.17 */
+    static final ASN1ObjectIdentifier    id_shake128_len = hashAlgs.branch("17");
+    /** 2.16.840.1.101.3.4.2.18 */
+    static final ASN1ObjectIdentifier    id_shake256_len = hashAlgs.branch("18");
+    /** 2.16.840.1.101.3.4.2.19 */
+    static final ASN1ObjectIdentifier    id_KmacWithSHAKE128 = hashAlgs.branch("19");
+    /** 2.16.840.1.101.3.4.2.20 */
+    static final ASN1ObjectIdentifier    id_KmacWithSHAKE256 = hashAlgs.branch("20");
 
     /** 2.16.840.1.101.3.4.1 */
     static final ASN1ObjectIdentifier    aes                     = nistAlgorithm.branch("1");
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
index fa32068..e6630c5 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ntt/NTTObjectIdentifiers.java
@@ -3,7 +3,7 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
- * From <a href="http://tools.ietf.org/html/rfc3657">RFC 3657</a>
+ * From <a href="https://tools.ietf.org/html/rfc3657">RFC 3657</a>
  * Use of the Camellia Encryption Algorithm
  * in Cryptographic Message Syntax (CMS)
  */
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
index 577e413..d52cfcc 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPObjectIdentifiers.java
@@ -3,7 +3,7 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 
 /**
- * OIDs for <a href="http://tools.ietf.org/html/rfc2560">RFC 2560</a> and <a href="http://tools.ietf.org/html/rfc6960">RFC 6960</a>
+ * OIDs for <a href="https://tools.ietf.org/html/rfc2560">RFC 2560</a> and <a href="https://tools.ietf.org/html/rfc6960">RFC 6960</a>
  * Online Certificate Status Protocol - OCSP.
  */
 public interface OCSPObjectIdentifiers
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java b/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
index ee23807..e44bd9b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/ocsp/OCSPResponseStatus.java
@@ -78,6 +78,11 @@
         return null;
     }
 
+    public int getIntValue()
+    {
+        return value.intValueExact();
+    }
+
     public BigInteger getValue()
     {
         return value.getValue();
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/CertBag.java b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/CertBag.java
index f5addb9..f43f472 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/CertBag.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/CertBag.java
@@ -19,7 +19,7 @@
     private CertBag(
         ASN1Sequence    seq)
     {
-        this.certId = (ASN1ObjectIdentifier)seq.getObjectAt(0);
+        this.certId = ASN1ObjectIdentifier.getInstance(seq.getObjectAt(0));
         this.certValue = ASN1TaggedObject.getInstance(seq.getObjectAt(1)).getObject();
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
index f794f73..981a281 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java
@@ -253,6 +253,14 @@
      */
     ASN1ObjectIdentifier id_rsa_KEM              = id_alg.branch("14");
 
+
+    /**
+     * id-alg-hss-lms-hashsig OBJECT IDENTIFIER ::= { iso(1)
+     *     member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+     *    smime(16) alg(3) 17 }
+     */
+    public static final ASN1ObjectIdentifier id_alg_hss_lms_hashsig = id_alg.branch("17");
+
     /**
      * <pre>
      * id-alg-AEADChaCha20Poly1305 OBJECT IDENTIFIER ::=
@@ -264,6 +272,30 @@
      */
     ASN1ObjectIdentifier id_alg_AEADChaCha20Poly1305 = id_alg.branch("18");
 
+    /**
+     * <pre>
+     *    id-alg-hkdf-with-sha256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+     *        us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) alg(3) 28 }
+     * </pre>
+     */
+    ASN1ObjectIdentifier id_alg_hkdf_with_sha256 = id_alg.branch("28");
+
+    /**
+     * <pre>
+     *    id-alg-hkdf-with-sha384 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+     *        us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) alg(3) 29 }
+     * </pre>
+     */
+    ASN1ObjectIdentifier id_alg_hkdf_with_sha384 = id_alg.branch("29");
+
+    /**
+     * <pre>
+     *    id-alg-hkdf-with-sha512 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+     *        us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) alg(3) 30 }
+     * </pre>
+     */
+    ASN1ObjectIdentifier id_alg_hkdf_with_sha512 = id_alg.branch("30");
+
     //
     // id-cti OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
     // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) cti(6)}
@@ -295,7 +327,7 @@
     /** PKCS#9: 1.2.840.113549.1.9.16.2.1 -- smime attribute receiptRequest */
     ASN1ObjectIdentifier id_aa_receiptRequest = id_aa.branch("1");
     
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.4 - See <a href="http://tools.ietf.org/html/rfc2634">RFC 2634</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.4 - See <a href="https://tools.ietf.org/html/rfc2634">RFC 2634</a> */
     ASN1ObjectIdentifier id_aa_contentHint      = id_aa.branch("4"); // See RFC 2634
     /** PKCS#9: 1.2.840.113549.1.9.16.2.5 */
     ASN1ObjectIdentifier id_aa_msgSigDigest     = id_aa.branch("5");
@@ -312,40 +344,40 @@
     /** PKCS#9: 1.2.840.113549.1.9.16.2.47 */
     ASN1ObjectIdentifier id_aa_signingCertificateV2 = id_aa.branch("47");
 
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.7 - See <a href="http://tools.ietf.org/html/rfc2634">RFC 2634</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.7 - See <a href="https://tools.ietf.org/html/rfc2634">RFC 2634</a> */
     ASN1ObjectIdentifier id_aa_contentIdentifier = id_aa.branch("7"); // See RFC 2634
 
     /*
      * RFC 3126
      */
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.14 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.14 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_signatureTimeStampToken = id_aa.branch("14");
     
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.15 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.15 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_sigPolicyId = id_aa.branch("15");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.16 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.16 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_commitmentType = id_aa.branch("16");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.17 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.17 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_signerLocation = id_aa.branch("17");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.18 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.18 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_signerAttr = id_aa.branch("18");
-    /** PKCS#9: 1.2.840.113549.1.9.16.6.2.19 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.6.2.19 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_otherSigCert = id_aa.branch("19");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.20 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.20 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_contentTimestamp = id_aa.branch("20");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.21 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.21 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_certificateRefs = id_aa.branch("21");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.22 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.22 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_revocationRefs = id_aa.branch("22");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.23 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.23 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_certValues = id_aa.branch("23");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.24 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.24 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_revocationValues = id_aa.branch("24");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.25 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.25 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_escTimeStamp = id_aa.branch("25");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.26 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.26 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_certCRLTimestamp = id_aa.branch("26");
-    /** PKCS#9: 1.2.840.113549.1.9.16.2.27 - <a href="http://tools.ietf.org/html/rfc3126">RFC 3126</a> */
+    /** PKCS#9: 1.2.840.113549.1.9.16.2.27 - <a href="https://tools.ietf.org/html/rfc3126">RFC 3126</a> */
     ASN1ObjectIdentifier id_aa_ets_archiveTimestamp = id_aa.branch("27");
 
     /** PKCS#9: 1.2.840.113549.1.9.16.2.37 - <a href="https://tools.ietf.org/html/rfc4108#section-2.2.5">RFC 4108</a> */
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
index 0219cb3..9ac8c5e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/pkcs/PrivateKeyInfo.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.asn1.pkcs;
 
 import java.io.IOException;
-import java.math.BigInteger;
 import java.util.Enumeration;
 
 import org.bouncycastle.asn1.ASN1BitString;
@@ -176,6 +175,11 @@
         }
     }
 
+    public ASN1Integer getVersion()
+    {
+        return version;
+    }
+
     public ASN1Set getAttributes()
     {
         return attributes;
@@ -186,6 +190,11 @@
         return privateKeyAlgorithm;
     }
 
+    public ASN1OctetString getPrivateKey()
+    {
+        return new DEROctetString(privateKey.getOctets());
+    }
+
     public ASN1Encodable parsePrivateKey()
         throws IOException
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java b/bcprov/src/main/java/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
index 5680b0b..8d4a267 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/teletrust/TeleTrusTNamedCurves.java
@@ -15,7 +15,7 @@
 
 /**
  * Elliptic curves defined in "ECC Brainpool Standard Curves and Curve Generation"
- * http://www.ecc-brainpool.org/download/draft_pkix_additional_ecc_dp.txt
+ * https://www.ecc-brainpool.org/download/draft_pkix_additional_ecc_dp.txt
  */
 public class TeleTrusTNamedCurves
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/ASN1IntegerTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/ASN1IntegerTest.java
index c9eaed4..ab1e605 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/ASN1IntegerTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/ASN1IntegerTest.java
@@ -5,6 +5,7 @@
 import org.bouncycastle.asn1.ASN1Enumerated;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.Properties;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
@@ -47,7 +48,7 @@
 
         new ASN1Integer(Hex.decode("ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e"));
 
-        new ASN1Enumerated(Hex.decode("ffda47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e"));
+        new ASN1Enumerated(Hex.decode("005a47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e"));
 
         System.setProperty("org.bouncycastle.asn1.allow_unsafe_integer", "false");
         
@@ -100,6 +101,17 @@
         {
             isEquals("malformed enumerated", e.getMessage());
         }
+
+        try
+        {
+            new ASN1Enumerated(Hex.decode("005a47bfc776bcd269da4832626ac332adfca6dd835e8ecd83cd1ebe7d709b0e"));
+
+            fail("no exception");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isEquals("malformed enumerated", e.getMessage());
+        }
     }
 
     /**
@@ -115,6 +127,7 @@
         byte[] rawInt = Hex.decode("10");
         ASN1Integer i = new ASN1Integer(rawInt);
         isEquals(i.getValue().intValue(), 16);
+        isEquals(i.intValueExact(), 16);
 
         //
         // With property set.
@@ -124,7 +137,7 @@
         rawInt = Hex.decode("10");
         i = new ASN1Integer(rawInt);
         isEquals(i.getValue().intValue(), 16);
-
+        isEquals(i.intValueExact(), 16);
     }
 
     public void testValidEncodingMultiByte()
@@ -137,6 +150,7 @@
         byte[] rawInt = Hex.decode("10FF");
         ASN1Integer i = new ASN1Integer(rawInt);
         isEquals(i.getValue().intValue(), 4351);
+        isEquals(i.intValueExact(), 4351);
 
         //
         // With property set.
@@ -146,7 +160,7 @@
         rawInt = Hex.decode("10FF");
         i = new ASN1Integer(rawInt);
         isEquals(i.getValue().intValue(), 4351);
-
+        isEquals(i.intValueExact(), 4351);
     }
 
     public void testInvalidEncoding_00()
@@ -156,8 +170,7 @@
         try
         {
             byte[] rawInt = Hex.decode("0010FF");
-            ASN1Integer i = new ASN1Integer(rawInt);
-            isEquals(i.getValue().intValue(), 4351);
+            new ASN1Integer(rawInt);
             fail("Expecting illegal argument exception.");
         }
         catch (IllegalArgumentException e)
@@ -173,7 +186,7 @@
         try
         {
             byte[] rawInt = Hex.decode("FF81FF");
-            ASN1Integer i = new ASN1Integer(rawInt);
+            new ASN1Integer(rawInt);
             fail("Expecting illegal argument exception.");
         }
         catch (IllegalArgumentException e)
@@ -192,8 +205,7 @@
         try
         {
             byte[] rawInt = Hex.decode("0000000010FF");
-            ASN1Integer i = new ASN1Integer(rawInt);
-            isEquals(i.getValue().intValue(), 4351);
+            new ASN1Integer(rawInt);
             fail("Expecting illegal argument exception.");
         }
         catch (IllegalArgumentException e)
@@ -212,7 +224,7 @@
         try
         {
             byte[] rawInt = Hex.decode("FFFFFFFF01FF");
-            ASN1Integer i = new ASN1Integer(rawInt);
+            new ASN1Integer(rawInt);
             fail("Expecting illegal argument exception.");
         }
         catch (IllegalArgumentException e)
@@ -277,8 +289,7 @@
         System.getProperties().put("org.bouncycastle.asn1.allow_unsafe_integer", "true");
         byte[] rawInt = Hex.decode("00000010FF000000");
         ASN1Integer i = new ASN1Integer(rawInt);
-        isEquals(72997666816L, i.getValue().longValue());
-
+        isEquals(72997666816L, BigIntegers.longValueExact(i.getValue()));
     }
 
     public void testLooseValidEncoding_FF_32BAligned()
@@ -291,8 +302,7 @@
         System.getProperties().put("org.bouncycastle.asn1.allow_unsafe_integer", "true");
         byte[] rawInt = Hex.decode("FFFFFF10FF000000");
         ASN1Integer i = new ASN1Integer(rawInt);
-        isEquals(-1026513960960L, i.getValue().longValue());
-
+        isEquals(-1026513960960L, BigIntegers.longValueExact(i.getValue()));
     }
 
     public void testLooseValidEncoding_FF_32BAligned_1not0()
@@ -306,8 +316,7 @@
         System.getProperties().put("org.bouncycastle.asn1.allow_unsafe_integer", "true");
         byte[] rawInt = Hex.decode("FFFEFF10FF000000");
         ASN1Integer i = new ASN1Integer(rawInt);
-        isEquals(-282501490671616L, i.getValue().longValue());
-
+        isEquals(-282501490671616L, BigIntegers.longValueExact(i.getValue()));
     }
 
     public void testLooseValidEncoding_FF_32BAligned_2not0()
@@ -321,7 +330,7 @@
         System.getProperties().put("org.bouncycastle.asn1.allow_unsafe_integer", "true");
         byte[] rawInt = Hex.decode("FFFFFE10FF000000");
         ASN1Integer i = new ASN1Integer(rawInt);
-        isEquals(-2126025588736L, i.getValue().longValue());
+        isEquals(-2126025588736L, BigIntegers.longValueExact(i.getValue()));
     }
 
     public void testOversizedEncoding()
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/BitStringTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/BitStringTest.java
index 4c49f32..8b31900 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/BitStringTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/BitStringTest.java
@@ -40,9 +40,9 @@
         }
         catch (NullPointerException e)
         {
-            if (!"data cannot be null".equals(e.getMessage()))
+            if (!"'data' cannot be null".equals(e.getMessage()))
             {
-                fail("Unexpected exception");
+                fail("Unexpected exception: " + e.getMessage());
             }
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/CMCCertificationRequestTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/CMCCertificationRequestTest.java
index 57e23b4..510dda1 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/CMCCertificationRequestTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/CMCCertificationRequestTest.java
@@ -1,8 +1,6 @@
 package org.bouncycastle.asn1.test;
 
-import java.io.ByteArrayOutputStream;
-
-import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.cmc.CertificationRequest;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Base64;
@@ -39,14 +37,9 @@
         byte[]  req)
         throws Exception
     {
-        CertificationRequest    r = CertificationRequest.getInstance(req);
+        CertificationRequest r = CertificationRequest.getInstance(req);
 
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        DEROutputStream          dOut = new DEROutputStream(bOut);
-
-        dOut.writeObject(r.toASN1Primitive());
-
-        byte[]                    bytes = bOut.toByteArray();
+        byte[] bytes = r.getEncoded(ASN1Encoding.DER);
 
         if (bytes.length != req.length)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/CMSTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/CMSTest.java
index 873b27c..f3e4b17 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/CMSTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/CMSTest.java
@@ -7,6 +7,7 @@
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
 import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.ASN1SetParser;
@@ -148,24 +149,18 @@
     {
         try
         {
-            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(compData));
-            
-            ContentInfo     info = ContentInfo.getInstance(aIn.readObject());
-            CompressedData  data = CompressedData.getInstance(info.getContent());
-            
+            ContentInfo info = ContentInfo.getInstance(ASN1Primitive.fromByteArray(compData));
+            CompressedData data = CompressedData.getInstance(info.getContent());
+
             data = new CompressedData(data.getCompressionAlgorithmIdentifier(), data.getEncapContentInfo());
             info = new ContentInfo(CMSObjectIdentifiers.compressedData, data);
-            
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-            
-            aOut.writeObject(info);
-            
-            if (!isSameAs(bOut.toByteArray(), compData))
+
+            byte[] encoding = info.getEncoded();
+            if (!isSameAs(encoding, compData))
             {
                 return new SimpleTestResult(false, getName() + ": CMS compression failed to re-encode");
             }
-            
+
             return new SimpleTestResult(true, getName() + ": Okay");
         }
         catch (Exception e)
@@ -181,25 +176,24 @@
             //
             // Key trans
             //
-            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(envDataKeyTrns));
-            
-            ContentInfo     info = ContentInfo.getInstance(aIn.readObject());
-            EnvelopedData   envData = EnvelopedData.getInstance(info.getContent());
-            ASN1Set         s = envData.getRecipientInfos();
-            
+            ContentInfo info = ContentInfo.getInstance(ASN1Primitive.fromByteArray(envDataKeyTrns));
+            EnvelopedData envData = EnvelopedData.getInstance(info.getContent());
+            ASN1Set s = envData.getRecipientInfos();
+
             if (s.size() != 1)
             {
                 return new SimpleTestResult(false, getName() + ": CMS KeyTrans enveloped, wrong number of recipients");
             }
-            
-            RecipientInfo   recip = RecipientInfo.getInstance(s.getObjectAt(0));
+
+            RecipientInfo recip = RecipientInfo.getInstance(s.getObjectAt(0));
 
             if (recip.getInfo() instanceof KeyTransRecipientInfo)
             {
-                KeyTransRecipientInfo   inf = KeyTransRecipientInfo.getInstance(recip.getInfo());
-                
-                inf = new KeyTransRecipientInfo(inf.getRecipientIdentifier(), inf.getKeyEncryptionAlgorithm(), inf.getEncryptedKey());
-                
+                KeyTransRecipientInfo inf = KeyTransRecipientInfo.getInstance(recip.getInfo());
+
+                inf = new KeyTransRecipientInfo(inf.getRecipientIdentifier(), inf.getKeyEncryptionAlgorithm(),
+                    inf.getEncryptedKey());
+
                 s = new DERSet(new RecipientInfo(inf));
             }
             else
@@ -207,15 +201,11 @@
                 return new SimpleTestResult(false, getName() + ": CMS KeyTrans enveloped, wrong recipient type");
             }
 
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-            
             envData = new EnvelopedData(envData.getOriginatorInfo(), s, envData.getEncryptedContentInfo(), envData.getUnprotectedAttrs());
             info = new ContentInfo(CMSObjectIdentifiers.envelopedData, envData);
-            
-            aOut.writeObject(info);
-            
-            if (!isSameAs(bOut.toByteArray(), envDataKeyTrns))
+
+            byte[] encoding = info.getEncoded();
+            if (!isSameAs(encoding, envDataKeyTrns))
             {
                 return new SimpleTestResult(false, getName() + ": CMS KeyTrans enveloped failed to re-encode");
             }
@@ -223,42 +213,37 @@
             //
             // KEK
             //
-            aIn = new ASN1InputStream(new ByteArrayInputStream(envDataKEK));
-            
-            info = ContentInfo.getInstance(aIn.readObject());
+            info = ContentInfo.getInstance(ASN1Primitive.fromByteArray(envDataKEK));
             envData = EnvelopedData.getInstance(info.getContent());
             s = envData.getRecipientInfos();
-            
+
             if (s.size() != 1)
             {
                 return new SimpleTestResult(false, getName() + ": CMS KEK enveloped, wrong number of recipients");
             }
-            
+
             recip = RecipientInfo.getInstance(s.getObjectAt(0));
 
             if (recip.getInfo() instanceof KEKRecipientInfo)
             {
-                KEKRecipientInfo   inf = KEKRecipientInfo.getInstance(recip.getInfo());
-                
+                KEKRecipientInfo inf = KEKRecipientInfo.getInstance(recip.getInfo());
+
                 inf = new KEKRecipientInfo(inf.getKekid(), inf.getKeyEncryptionAlgorithm(), inf.getEncryptedKey());
-                
+
                 s = new DERSet(new RecipientInfo(inf));
             }
             else
             {
                 return new SimpleTestResult(false, getName() + ": CMS KEK enveloped, wrong recipient type");
             }
-            
-            bOut = new ByteArrayOutputStream();
-            aOut = new ASN1OutputStream(bOut);
-            
-            envData = new EnvelopedData(envData.getOriginatorInfo(), s, envData.getEncryptedContentInfo(), envData.getUnprotectedAttrs());
+
+            envData = new EnvelopedData(envData.getOriginatorInfo(), s, envData.getEncryptedContentInfo(),
+                envData.getUnprotectedAttrs());
             info = new ContentInfo(CMSObjectIdentifiers.envelopedData, envData);
-            
-            aOut.writeObject(info);
-            
-            if (!isSameAs(bOut.toByteArray(), envDataKEK))
-            {                                                         System.out.println(new String(Base64.encode(bOut.toByteArray())));
+
+            encoding = info.getEncoded();
+            if (!isSameAs(encoding, envDataKEK))
+            {
                 return new SimpleTestResult(false, getName() + ": CMS KEK enveloped failed to re-encode");
             }
 
@@ -293,29 +278,24 @@
             return new SimpleTestResult(false, getName() + ": CMS enveloped failed - " + e.toString(), e);
         }
     }
-    
+
     private TestResult signedTest()
     {
         try
         {
-            ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(signedData));
-            
-            ContentInfo     info = ContentInfo.getInstance(aIn.readObject());
-            SignedData      sData = SignedData.getInstance(info.getContent());
-            
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-            
-            sData = new SignedData(sData.getDigestAlgorithms(), sData.getEncapContentInfo(), sData.getCertificates(), sData.getCRLs(), sData.getSignerInfos());
+            ContentInfo info = ContentInfo.getInstance(ASN1Primitive.fromByteArray(signedData));
+            SignedData sData = SignedData.getInstance(info.getContent());
+
+            sData = new SignedData(sData.getDigestAlgorithms(), sData.getEncapContentInfo(), sData.getCertificates(),
+                sData.getCRLs(), sData.getSignerInfos());
             info = new ContentInfo(CMSObjectIdentifiers.signedData, sData);
-            
-            aOut.writeObject(info);
-            
-            if (!isSameAs(bOut.toByteArray(), signedData))
+
+            byte[] encoding = info.getEncoded();
+            if (!isSameAs(encoding, signedData))
             {
                 return new SimpleTestResult(false, getName() + ": CMS signed failed to re-encode");
             }
-            
+
             return new SimpleTestResult(true, getName() + ": Okay");
         }
         catch (Exception e)
@@ -323,7 +303,7 @@
             return new SimpleTestResult(false, getName() + ": CMS signed failed - " + e.toString(), e);
         }
     }
-    
+
     public TestResult perform()
     {
         TestResult  res = compressionTest();
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/CertificateTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/CertificateTest.java
index 6aa8cef..676d5d5 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/CertificateTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/CertificateTest.java
@@ -253,6 +253,10 @@
         "/5LdslDjfqV2nUc2GqDPn38PATL26SRJKlCvU2NagdID3WM="
     );
 
+    // malformed cert
+    byte[] bangerCert = Base64.decode("MIIBSKADAgECAgECMA0GCSqGSIb3DQEEBAUAMCUxCzAJBgNVBAMMAkFVMRYwFAYDVQQKDA1CaXVuYHkgQGFzdGtlMB4XDTcwMDExMTQyNjAwMVoXDTcwMDEwNzAwMDAwMlowNjELMQkGA1UBAwwCQVUxFjAUBgNVAQwMDUJsdW5jeSZDY3Nzb2UxDzANBgNVAQsMBlRlc3cgNTAYMBAGBisOBwMDATAGAgEBAgECAwQAAgEDoYGVMIGSMGEGA1YdIwEB/wRXNVWAFDZPdpTPzKi7o8EJokoQU2uqCHRRoTqkOzA2NAs2CgYDVQYDDAJHVTEWMBQGA1QECQwNQmhwbmR5J0Ngc3RsYDAPMA0CA1UECwwGUWVzdyA0hQECMCAGA1UdDgEB/wQWBBQ2T3OSzciou6PBCqRJEFNrqgh2UTALBgNVHQkEBAMGBBE=\n");
+
+
    String[] subjects = 
    {
        "C=AU,ST=Victoria,L=South Melbourne,O=Connect 4 Pty Ltd,OU=Webserver Team,CN=www2.connect4.com.au,E=webmaster@connect4.com.au",
@@ -579,6 +583,18 @@
         }
     }
 
+    private void checkMalformed()
+    {
+        try
+        {
+            TBSCertificate cert = TBSCertificate.getInstance(bangerCert);
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected - anything else is not!
+        }
+    }
+    
     public void performTest()
         throws Exception
     {
@@ -592,6 +608,7 @@
         checkAttributeCertificate(8,cert8);
         checkV1AttributeCertificate(9, attrCertv1);
         checkDudCertificate();
+        checkMalformed();
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/CertifiedKeyPairTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/CertifiedKeyPairTest.java
index c3b0dbf..5c990c0 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/CertifiedKeyPairTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/CertifiedKeyPairTest.java
@@ -6,6 +6,7 @@
 import org.bouncycastle.asn1.cmp.CMPCertificate;
 import org.bouncycastle.asn1.cmp.CertOrEncCert;
 import org.bouncycastle.asn1.cmp.CertifiedKeyPair;
+import org.bouncycastle.asn1.crmf.EncryptedKey;
 import org.bouncycastle.asn1.crmf.EncryptedValue;
 import org.bouncycastle.asn1.crmf.PKIPublicationInfo;
 import org.bouncycastle.asn1.x509.Certificate;
@@ -64,7 +65,7 @@
         encEqualTest(ckp);
 
         PKIPublicationInfo pubInfo = new PKIPublicationInfo(PKIPublicationInfo.dontPublish);
-        ckp = new CertifiedKeyPair(certOrEncCert, null, pubInfo);
+        ckp = new CertifiedKeyPair(certOrEncCert, (EncryptedKey)null, pubInfo);
 
         isEquals(certOrEncCert, ckp.getCertOrEncCert());
         isTrue(null == ckp.getPrivateKey());
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/DLExternalTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/DLExternalTest.java
new file mode 100644
index 0000000..0bdd861
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/DLExternalTest.java
@@ -0,0 +1,230 @@
+package org.bouncycastle.asn1.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERPrintableString;
+import org.bouncycastle.asn1.DERUTF8String;
+import org.bouncycastle.asn1.DLApplicationSpecific;
+import org.bouncycastle.asn1.DLExternal;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.DLSet;
+import org.bouncycastle.asn1.DLTaggedObject;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Class checking the correct functionality of DLExternal
+ */
+public class DLExternalTest
+    extends SimpleTest
+{
+
+    /**
+     * Checks that the values are correctly instantiated
+     *
+     * @throws Exception Will be thrown if there was an
+     *                   error while performing the test
+     */
+    public void testInstantiationByVector()
+        throws Exception
+    {
+        ASN1EncodableVector vec = new ASN1EncodableVector();
+        String dvdType;
+        String ecType;
+        try
+        {
+            new DLExternal(vec);
+            fail("exception expected");
+        }
+        catch (IllegalArgumentException iae)
+        {
+            isEquals("check message", "too few objects in input vector", iae.getMessage());
+        }
+
+        vec.add(new DERUTF8String("something completely different"));
+        try
+        {
+            new DLExternal(vec);
+            fail("exception expected");
+        }
+        catch (IllegalArgumentException iae)
+        {
+            isEquals("check message", "too few objects in input vector", iae.getMessage());
+        }
+        vec.add(new DLTaggedObject(true, 1, new ASN1Integer(1234567890L)));
+
+        DLExternal dle = new DLExternal(vec);
+
+        isEquals("check direct reference", null, dle.getDirectReference());
+        isEquals("check indirect reference", null, dle.getIndirectReference());
+        isTrue("check data value descriptor", dle.getDataValueDescriptor() != null);
+        dvdType = dle.getDataValueDescriptor().getClass().getName();
+        isEquals("check type of value descriptor: " + dvdType, DERUTF8String.class.getName(), dvdType);
+        isEquals("check value", "something completely different", ((DERUTF8String)dle.getDataValueDescriptor()).getString());
+        isEquals("check encoding", 1, dle.getEncoding());
+        isTrue("check existence of external content", dle.getExternalContent() != null);
+        ecType = dle.getExternalContent().getClass().getName();
+        isEquals("check type of external content: " + ecType, ASN1Integer.class.getName(), ecType);
+        isEquals("check value of external content", "1234567890", ((ASN1Integer)dle.getExternalContent()).getValue().toString());
+
+        vec = new ASN1EncodableVector();
+        vec.add(new ASN1Integer(9L));
+        vec.add(new DERUTF8String("something completely different"));
+        vec.add(new DLTaggedObject(true, 1, new ASN1Integer(1234567890L)));
+        dle = new DLExternal(vec);
+
+        isEquals("check direct reference", null, dle.getDirectReference());
+        isTrue("check existence of indirect reference", dle.getIndirectReference() != null);
+        isEquals("check indirect reference", "9", dle.getIndirectReference().getValue().toString());
+        isTrue("check existence of data value descriptor", dle.getDataValueDescriptor() != null);
+        dvdType = dle.getDataValueDescriptor().getClass().getName();
+        isEquals("check type of value descriptor: " + dvdType, DERUTF8String.class.getName(), dvdType);
+        isEquals("check value", "something completely different", ((DERUTF8String)dle.getDataValueDescriptor()).getString());
+        isEquals("check encoding", 1, dle.getEncoding());
+        isTrue("check existence of external content", dle.getExternalContent() != null);
+        ecType = dle.getExternalContent().getClass().getName();
+        isEquals("check type of external content: " + ecType, ASN1Integer.class.getName(), ecType);
+        isEquals("check value of external content", "1234567890", ((ASN1Integer)dle.getExternalContent()).getValue().toString());
+
+        dle = new DLExternal(createRealDataExample());
+        checkRealDataExample(dle);
+    }
+
+    /**
+     * Checks that a DLExternal is created from DER encoded bytes correctly.
+     * This is done by creating the DER encoded data by using <code>getEncoded</code>
+     *
+     * @throws Exception Will be thrown if there was an
+     *                   error while performing the test
+     */
+    public void testReadEncoded()
+        throws Exception
+    {
+        DLExternal dle = new DLExternal(createRealDataExample());
+
+        ASN1InputStream ais = new ASN1InputStream(dle.getEncoded());
+        ASN1Primitive ap = ais.readObject();
+        isTrue("check ais returned an object", ap != null);
+        isEquals("check returned type: " + ap.getClass(), DLExternal.class.getName(), ap.getClass().getName());
+        checkRealDataExample((DLExternal)ap);
+        ais.close();
+    }
+
+    private void checkRealDataExample(DLExternal dle)
+        throws IOException
+    {
+        //System.out.println(ASN1Dump.dumpAsString(dle, true));
+        isEquals("check direct reference", "2.1.1", String.valueOf(dle.getDirectReference()));
+        isEquals("check indirect reference", "9", String.valueOf(dle.getIndirectReference()));
+        isEquals("check data value decriptor", "example data representing the User Data of an OSI.6 ConnectP containing an MSBind with username and password", String.valueOf(dle.getDataValueDescriptor()));
+        isEquals("check encoding", 2, dle.getEncoding());
+
+        ASN1Primitive content = dle.getExternalContent();
+        isTrue("check existence of content", content != null);
+        isTrue("check type is a tagged object: " + content.getClass(), content instanceof ASN1TaggedObject);
+
+        ASN1TaggedObject msBind = (ASN1TaggedObject)content;
+        isEquals("check tag", 16, msBind.getTagNo());
+        isEquals("check explicit", true, msBind.isExplicit());
+        isEquals("check tagged object is a DLSet: " + msBind.getObject().getClass(), DLSet.class.getName(), msBind.getObject().getClass().getName());
+
+        DLSet msBindSet = (DLSet)msBind.getObject();
+        isEquals("check number of elements", 2, msBindSet.size());
+        isEquals("check first element in set: " + msBindSet.getObjectAt(0).getClass(), DLApplicationSpecific.class.getName(), msBindSet.getObjectAt(0).getClass().getName());
+
+        DLApplicationSpecific objectName = (DLApplicationSpecific)msBindSet.getObjectAt(0);
+        isEquals("check tag number", 0, objectName.getApplicationTag());
+        isEquals("check application object: " + objectName.getObject().getClass(), DLSequence.class.getName(), objectName.getObject().getClass().getName());
+        DLSequence objNameElems = (DLSequence)objectName.getObject();
+        isEquals("check number of elements", 4, objNameElems.size());
+        isEquals("check first element in set: " + objNameElems.getObjectAt(0).getClass(), DLApplicationSpecific.class.getName(), objNameElems.getObjectAt(0).getClass().getName());
+        DLApplicationSpecific objNameAppl = (DLApplicationSpecific)objNameElems.getObjectAt(0);
+        isEquals("check application number", 0, objNameAppl.getApplicationTag());
+        isEquals("check application object: " + objNameAppl.getObject().getClass(), DERPrintableString.class.getName(), objNameAppl.getObject().getClass().getName());
+        isEquals("check C", "de", ((DERPrintableString)objNameAppl.getObject()).getString());
+        isEquals("check second element in set: " + objNameElems.getObjectAt(1).getClass(), DLApplicationSpecific.class.getName(), objNameElems.getObjectAt(1).getClass().getName());
+        objNameAppl = (DLApplicationSpecific)objNameElems.getObjectAt(1);
+        isEquals("check application number", 2, objNameAppl.getApplicationTag());
+        isEquals("check application object: " + objNameAppl.getObject().getClass(), DERPrintableString.class.getName(), objNameAppl.getObject().getClass().getName());
+        isEquals("check A", "viaT", ((DERPrintableString)objNameAppl.getObject()).getString());
+        isEquals("check third element in set: " + objNameElems.getObjectAt(2).getClass(), DLTaggedObject.class.getName(), objNameElems.getObjectAt(2).getClass().getName());
+        DLTaggedObject objNameTagged = (DLTaggedObject)objNameElems.getObjectAt(2);
+        isEquals("check tag number", 3, objNameTagged.getTagNo());
+        isEquals("check implicit", false, objNameTagged.isExplicit());
+        isEquals("check tagged object: " + objNameTagged.getObject().getClass(), DEROctetString.class.getName(), objNameTagged.getObject().getClass().getName());
+        isEquals("check O", "Organization", new String(((DEROctetString)objNameTagged.getObject()).getOctets(), "8859_1"));
+        isEquals("check fourth element in set: " + objNameElems.getObjectAt(3).getClass(), DLTaggedObject.class.getName(), objNameElems.getObjectAt(3).getClass().getName());
+        objNameTagged = (DLTaggedObject)objNameElems.getObjectAt(3);
+        isEquals("check tag number", 5, objNameTagged.getTagNo());
+        isEquals("check implicit", true, objNameTagged.isExplicit());
+        isEquals("check tagged object: " + objNameTagged.getObject().getClass(), DLTaggedObject.class.getName(), objNameTagged.getObject().getClass().getName());
+        objNameTagged = (DLTaggedObject)objNameTagged.getObject();
+        isEquals("check tag number", 0, objNameTagged.getTagNo());
+        isEquals("check implicit", false, objNameTagged.isExplicit());
+        isEquals("check tagged object: " + objNameTagged.getObject().getClass(), DEROctetString.class.getName(), objNameTagged.getObject().getClass().getName());
+        isEquals("check CN", "Common Name", new String(((DEROctetString)objNameTagged.getObject()).getOctets(), "8859_1"));
+
+        isEquals("check second element in set: " + msBind.getObject().getClass(), DLTaggedObject.class.getName(), msBindSet.getObjectAt(1).getClass().getName());
+        DLTaggedObject password = (DLTaggedObject)msBindSet.getObjectAt(1);
+        isEquals("check tag number", 2, password.getTagNo());
+        isEquals("check explicit", true, password.isExplicit());
+        isEquals("check tagged object: " + password.getObject().getClass(), DERIA5String.class.getName(), password.getObject().getClass().getName());
+        isEquals("check password", "SomePassword", ((DERIA5String)password.getObject()).getString());
+    }
+
+    private ASN1EncodableVector createRealDataExample()
+        throws IOException
+    {
+        ASN1EncodableVector vec = new ASN1EncodableVector();
+
+        vec.add(new ASN1ObjectIdentifier("2.1.1"));
+        vec.add(new ASN1Integer(9));
+        vec.add(new DERUTF8String("example data representing the User Data of an OSI.6 ConnectP containing an MSBind with username and password"));
+
+        ASN1EncodableVector objectNameVec = new ASN1EncodableVector();
+        objectNameVec.add(new DLApplicationSpecific(0, new DERPrintableString("de")));
+        objectNameVec.add(new DLApplicationSpecific(2, new DERPrintableString("viaT")));
+        objectNameVec.add(new DLTaggedObject(false, 3, new DEROctetString("Organization".getBytes("8859_1"))));
+        objectNameVec.add(new DLTaggedObject(true, 5, new DLTaggedObject(false, 0, new DEROctetString("Common Name".getBytes("8859_1")))));
+
+        DLApplicationSpecific objectName = new DLApplicationSpecific(0, new DLSequence(objectNameVec));
+        DLTaggedObject password = new DLTaggedObject(true, 2, new DERIA5String("SomePassword"));
+        ASN1EncodableVector msBindVec = new ASN1EncodableVector();
+        msBindVec.add(objectName);
+        msBindVec.add(password);
+        DLSet msBindSet = new DLSet(msBindVec);
+        DLTaggedObject msBind = new DLTaggedObject(true, 16, msBindSet);
+
+        vec.add(new DLTaggedObject(true, 2, msBind)); // the encoding is actually 0 but that's also the default
+        return vec;
+    }
+
+    public String getName()
+    {
+        return "DLExternal";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testInstantiationByVector();
+        testReadEncoded();
+    }
+
+    /**
+     * Main method to start testing manually outside production
+     *
+     * @param args Calling arguments (not used here)
+     */
+    public static void main(String[] args)
+    {
+        runTest(new DLExternalTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
index a9e7e42..98cf7d5 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/DeclarationOfMajorityUnitTest.java
@@ -3,8 +3,7 @@
 import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1GeneralizedTime;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.isismtt.x509.DeclarationOfMajority;
 
 public class DeclarationOfMajorityUnitTest
@@ -59,11 +58,7 @@
 
         checkValues(decl, type, dateOfBirth, notYoungerThan);
 
-        ASN1InputStream aIn = new ASN1InputStream(decl.toASN1Primitive().getEncoded());
-
-        DERTaggedObject info = (DERTaggedObject)aIn.readObject();
-
-        decl = DeclarationOfMajority.getInstance(info);
+        decl = DeclarationOfMajority.getInstance(ASN1Primitive.fromByteArray(decl.getEncoded()));
 
         checkValues(decl, type, dateOfBirth, notYoungerThan);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
index f179fa7..6d96971 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/EncryptedPrivateKeyInfoTest.java
@@ -1,11 +1,10 @@
 package org.bouncycastle.asn1.test;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.util.Strings;
@@ -15,7 +14,7 @@
 /**
  * Test the reading and writing of EncryptedPrivateKeyInfo objects using
  * the test vectors provided at
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
  * RSA's PKCS5 Page</a>.
  * <br>
  * The vectors are Base 64 encoded and encrypted using the password "password"
@@ -80,20 +79,16 @@
             fail("test " + id + " failed construction - exception " + e.toString(), e);
         }
 
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        DEROutputStream         dOut = new DEROutputStream(bOut);
-
+        byte[] bytes = null;
         try
         {
-            dOut.writeObject(info);
+            bytes = info.getEncoded(ASN1Encoding.DER);
         }
         catch (Exception e)
         {
             fail("test " + id + " failed writing - exception " + e.toString(), e);
         }
 
-        byte[]  bytes = bOut.toByteArray();
-
         if (bytes.length != sample.length)
         {
             try
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/EnumeratedTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/EnumeratedTest.java
index c1c3b3b..ebfa607 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/EnumeratedTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/EnumeratedTest.java
@@ -50,7 +50,7 @@
 
         assertNotNull("ENUMERATED expected", enumerated);
 
-        assertEquals("Unexpected ENUMERATED value", 1, enumerated.getValue().intValue());
+        assertEquals("Unexpected ENUMERATED value", 1, enumerated.intValueExact());
 
         ASN1Boolean b = ASN1Boolean.getInstance(sequence.getObjectAt(1));
 
@@ -77,13 +77,13 @@
 
         assertNotNull("ENUMERATED expected", enumerated1);
 
-        assertEquals("Unexpected ENUMERATED value", 257, enumerated1.getValue().intValue());
+        assertEquals("Unexpected ENUMERATED value", 257, enumerated1.intValueExact());
 
         ASN1Enumerated enumerated2 = ASN1Enumerated.getInstance(sequence.getObjectAt(1));
 
         assertNotNull("ENUMERATED expected", enumerated2);
 
-        assertEquals("Unexpected ENUMERATED value", 514, enumerated2.getValue().intValue());
+        assertEquals("Unexpected ENUMERATED value", 514, enumerated2.intValueExact());
     }
 
     /**
@@ -104,7 +104,7 @@
 
         assertNotNull("ENUMERATED expected", enumerated);
 
-        assertEquals("Unexpected ENUMERATED value", 65793, enumerated.getValue().intValue());
+        assertEquals("Unexpected ENUMERATED value", 65793, enumerated.intValueExact());
 
         ASN1ObjectIdentifier objectId = ASN1ObjectIdentifier.getInstance(sequence.getObjectAt(1));
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
index 1785ce0..cb3dbb0 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/EqualsAndHashCodeTest.java
@@ -11,7 +11,7 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.BERConstructedOctetString;
+import org.bouncycastle.asn1.BEROctetString;
 import org.bouncycastle.asn1.BERSequence;
 import org.bouncycastle.asn1.BERSet;
 import org.bouncycastle.asn1.BERTaggedObject;
@@ -49,21 +49,21 @@
         byte[]    data = { 0, 1, 0, 1, 0, 0, 1 };
         
         ASN1Primitive    values[] = {
-                new BERConstructedOctetString(data),
+                new BEROctetString(data),
                 new BERSequence(new DERPrintableString("hello world")),
                 new BERSet(new DERPrintableString("hello world")),
                 new BERTaggedObject(0, new DERPrintableString("hello world")),
                 new DERApplicationSpecific(0, data),
                 new DERBitString(data),
                 new DERBMPString("hello world"),
-                new ASN1Boolean(true),
-                new ASN1Boolean(false),
+                ASN1Boolean.getInstance(true),
+                ASN1Boolean.getInstance(false),
                 new ASN1Enumerated(100),
                 new DERGeneralizedTime("20070315173729Z"),
                 new DERGeneralString("hello world"),
                 new DERIA5String("hello"),
                 new ASN1Integer(1000),
-                new DERNull(),
+                DERNull.INSTANCE,
                 new DERNumericString("123456"),
                 new ASN1ObjectIdentifier("1.1.1.10000.1"),
                 new DEROctetString(data),
@@ -82,17 +82,17 @@
         
         try
         {
-            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-            ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-            
+            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+            ASN1OutputStream aOut = ASN1OutputStream.create(bOut);
+
             for (int i = 0; i != values.length; i++)
             {
                 aOut.writeObject(values[i]);
             }
 
-            ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
-            ASN1InputStream         aIn = new ASN1InputStream(bIn);
-            
+            ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
+            ASN1InputStream aIn = new ASN1InputStream(bIn);
+
             for (int i = 0; i != values.length; i++)
             {
                 ASN1Primitive o = aIn.readObject();
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/GeneralizedTimeTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
index 4663484..056bd2d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/GeneralizedTimeTest.java
@@ -160,12 +160,11 @@
         for (int i = 0; i != input.length; i++)
         {
             ASN1GeneralizedTime    t = new ASN1GeneralizedTime(input[i]);
-
             if (output[i].indexOf('G') > 0)   // don't check local time the same way
             {
                 if (!t.getTime().equals(output[i]))
                 {
-                    fail("failed conversion test");
+                    fail("failed GMT conversion test got " + t.getTime() + " expected " + output[i]);
                 }
                 if (!dateF.format(t.getDate()).equals(zOutput[i]))
                 {
@@ -177,7 +176,7 @@
                 String offset = calculateGMTOffset(t.getDate());
                 if (!t.getTime().equals(output[i] + offset))
                 {
-                    fail("failed conversion test");
+                    fail("failed conversion test got " + t.getTime() + " expected " + output[i] + offset);
                 }
             }
         }
@@ -215,6 +214,20 @@
                 fail("trunc der encoding wrong");
             }
         }
+
+        // check an actual GMT string comes back untampered
+        ASN1GeneralizedTime time = new ASN1GeneralizedTime("20190704031318GMT+00:00");
+
+        isTrue("20190704031318GMT+00:00".equals(time.getTime()));
+
+        try
+        {
+            new DERGeneralizedTime(new byte[0]);
+        }
+        catch (IllegalArgumentException e)
+        {
+            isTrue(e.getMessage().equals("GeneralizedTime string too short"));
+        }
     }
 
     private String calculateGMTOffset(Date date)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/GenerationTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/GenerationTest.java
index bf34fc7..30ceb5a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/GenerationTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/GenerationTest.java
@@ -1,7 +1,5 @@
 package org.bouncycastle.asn1.test;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.text.ParseException;
@@ -9,9 +7,7 @@
 
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1GeneralizedTime;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
-import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DEROctetString;
@@ -101,13 +97,10 @@
 
         gen.setSubjectPublicKeyInfo(info);
 
-        TBSCertificate              tbs = gen.generateTBSCertificate();
-        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-        ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
+        TBSCertificate tbs = gen.generateTBSCertificate();
 
-        aOut.writeObject(tbs);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v1Cert))
+        byte[] encoding = tbs.getEncoded();
+        if (!Arrays.areEqual(encoding, v1Cert))
         {
             fail("failed v1 cert generation");
         }
@@ -115,15 +108,10 @@
         //
         // read back test
         //
-        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v1Cert));
-        ASN1Primitive       o = aIn.readObject();
+        ASN1Primitive o = ASN1Primitive.fromByteArray(v1Cert);
 
-        bOut = new ByteArrayOutputStream();
-        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(o);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v1Cert))
+        encoding = o.getEncoded();
+        if (!Arrays.areEqual(encoding, v1Cert))
         {
             fail("failed v1 cert read back test");
         }
@@ -175,13 +163,10 @@
 
         gen.setExtensions(ex);
 
-        TBSCertificate              tbs = gen.generateTBSCertificate();
-        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-        ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
+        TBSCertificate tbs = gen.generateTBSCertificate();
 
-        aOut.writeObject(tbs);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v3Cert))
+        byte[] encoding = tbs.getEncoded();
+        if (!Arrays.areEqual(encoding, v3Cert))
         {
             fail("failed v3 cert generation");
         }
@@ -189,15 +174,10 @@
         //
         // read back test
         //
-        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v3Cert));
-        ASN1Primitive       o = aIn.readObject();
+        ASN1Primitive o = ASN1Primitive.fromByteArray(v3Cert);
 
-        bOut = new ByteArrayOutputStream();
-        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(o);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v3Cert))
+        encoding = o.getEncoded();
+        if (!Arrays.areEqual(encoding, v3Cert))
         {
             fail("failed v3 cert read back test");
         }
@@ -245,13 +225,10 @@
 
         gen.setExtensions(ex);
 
-        TBSCertificate              tbs = gen.generateTBSCertificate();
-        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-        ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
+        TBSCertificate tbs = gen.generateTBSCertificate();
 
-        aOut.writeObject(tbs);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v3CertNullSubject))
+        byte[] encoding = tbs.getEncoded();
+        if (!Arrays.areEqual(encoding, v3CertNullSubject))
         {
             fail("failed v3 null sub cert generation");
         }
@@ -259,15 +236,10 @@
         //
         // read back test
         //
-        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v3CertNullSubject));
-        ASN1Primitive       o = aIn.readObject();
+        ASN1Primitive o = ASN1Primitive.fromByteArray(v3CertNullSubject);
 
-        bOut = new ByteArrayOutputStream();
-        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(o);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v3CertNullSubject))
+        encoding = o.getEncoded();
+        if (!Arrays.areEqual(encoding, v3CertNullSubject))
         {
             fail("failed v3 null sub cert read back test");
         }
@@ -300,34 +272,44 @@
         extGen.addExtension(Extension.cRLNumber, false, new ASN1Integer(1));
         extGen.addExtension(Extension.issuingDistributionPoint, true, IssuingDistributionPoint.getInstance(new DERSequence()));
 
+        isTrue(extGen.hasExtension(Extension.cRLNumber));
+        isTrue(!extGen.hasExtension(Extension.freshestCRL));
+
+        isEquals(new Extension(Extension.cRLNumber, false, new ASN1Integer(1).getEncoded()), extGen.getExtension(Extension.cRLNumber));
+
         Extensions          ex = extGen.generate();
 
         gen.setExtensions(ex);
 
-        TBSCertList                 tbs = gen.generateTBSCertList();
-        ByteArrayOutputStream       bOut = new ByteArrayOutputStream();
-        ASN1OutputStream            aOut = new ASN1OutputStream(bOut);
+        TBSCertList tbs = gen.generateTBSCertList();
 
-        aOut.writeObject(tbs);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v2CertList))
+        byte[] encoding = tbs.getEncoded();
+        if (!Arrays.areEqual(encoding, v2CertList))
         {
-            System.out.println(new String(Base64.encode(bOut.toByteArray())));
+            System.out.println(new String(Base64.encode(encoding)));
             fail("failed v2 cert list generation");
         }
 
+        // extGen - check replacement.
+        extGen.replaceExtension(Extension.cRLNumber, false, new ASN1Integer(2));
+
+        isEquals(new Extension(Extension.cRLNumber, false, new ASN1Integer(2).getEncoded()), extGen.getExtension(Extension.cRLNumber));
+
+        // extGen - check remove.
+        extGen.removeExtension(Extension.cRLNumber);
+
+        isTrue(!extGen.hasExtension(Extension.cRLNumber));
+
+        // check we can still generate
+        ex = extGen.generate();
+        
         //
         // read back test
         //
-        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(v2CertList));
-        ASN1Primitive o = aIn.readObject();
+        ASN1Primitive o = ASN1Primitive.fromByteArray(v2CertList);
 
-        bOut = new ByteArrayOutputStream();
-        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(o);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), v2CertList))
+        encoding = o.getEncoded();
+        if (!Arrays.areEqual(encoding, v2CertList))
         {
             fail("failed v2 cert list read back test");
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/InputStreamTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/InputStreamTest.java
index 819d285..65c215f 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/InputStreamTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/InputStreamTest.java
@@ -3,26 +3,34 @@
 import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTest;
 
-public class InputStreamTest 
+public class InputStreamTest
     extends SimpleTest
 {
-    private static final byte[] outOfBoundsLength = new byte[] { (byte)0x30, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff };
-    private static final byte[] negativeLength = new byte[] { (byte)0x30, (byte)0x84, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff };
-    private static final byte[] outsideLimitLength = new byte[] { (byte)0x30, (byte)0x83, (byte)0x0f, (byte)0xff, (byte)0xff };
-    
-    
+    private static final byte[] outOfBoundsLength = new byte[]{(byte)0x30, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff};
+    private static final byte[] negativeLength = new byte[]{(byte)0x30, (byte)0x84, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff};
+    private static final byte[] outsideLimitLength = new byte[]{(byte)0x30, (byte)0x83, (byte)0x0f, (byte)0xff, (byte)0xff};
+
+    private static final byte[] classCast1 = Base64.decode("p1AkHmYAvfOEIrL4ESfrNg==");
+    private static final byte[] classCast2 = Base64.decode("JICNbaBUTTq7uxj5mg==");
+    private static final byte[] classCast3 = Base64.decode("JAKzADNCxhrrBSVS");
+    private static final byte[] memoryError1 = Base64.decode("vm66gOiEe+FV/NvujMwSkUp5Lffw5caQlaRU5sdMPC70IGWmyK2/");
+    private static final byte[] memoryError2 = Base64.decode("vm4ogOSEfVGsS3w+KTzb2A0ALYR8VBOQqQeuRwnsPC4AAGWEDLjd");
+
     public String getName()
     {
         return "InputStream";
     }
-    
-    public void performTest() 
+
+    public void performTest()
         throws Exception
     {
         ASN1InputStream aIn = new ASN1InputStream(outOfBoundsLength);
-        
+
         try
         {
             aIn.readObject();
@@ -35,9 +43,9 @@
                 fail("wrong exception: " + e.getMessage());
             }
         }
-        
+
         aIn = new ASN1InputStream(negativeLength);
-        
+
         try
         {
             aIn.readObject();
@@ -50,9 +58,9 @@
                 fail("wrong exception: " + e.getMessage());
             }
         }
-        
+
         aIn = new ASN1InputStream(outsideLimitLength);
-        
+
         try
         {
             aIn.readObject();
@@ -60,15 +68,44 @@
         }
         catch (IOException e)
         {
-            if (!e.getMessage().equals("corrupted stream - out of bounds length found"))
+            if (!e.getMessage().equals("corrupted stream - out of bounds length found: 1048575 >= 5"))
             {
                 fail("wrong exception: " + e.getMessage());
             }
         }
+
+        testWithByteArray(classCast1, "unknown object encountered: class org.bouncycastle.asn1.DLApplicationSpecific");
+        testWithByteArray(classCast2, "unknown object encountered: class org.bouncycastle.asn1.BERTaggedObjectParser");
+        testWithByteArray(classCast3, "unknown object encountered in constructed OCTET STRING: class org.bouncycastle.asn1.DLTaggedObject");
+
+        testWithByteArray(memoryError1, "corrupted stream - out of bounds length found: 2078365180 >= 39");
+        testWithByteArray(memoryError2, "corrupted stream - out of bounds length found: 2102504523 >= 39");
+    }
+
+    private void testWithByteArray(byte[] data, String message)
+    {
+        try
+        {
+            ASN1InputStream input = new ASN1InputStream(data);
+
+            ASN1Primitive p;
+            while ((p = input.readObject()) != null)
+            {
+                ASN1Sequence asn1 = ASN1Sequence.getInstance(p);
+                for (int i = 0; i < asn1.size(); i++)
+                {
+                    asn1.getObjectAt(i);
+                }
+            }
+        }
+        catch (java.io.IOException e)
+        {
+            isEquals(e.getMessage(), message, e.getMessage());
+        }
     }
 
     public static void main(
-        String[]    args)
+        String[] args)
     {
         runTest(new InputStreamTest());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/KMACParamsTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/KMACParamsTest.java
new file mode 100644
index 0000000..91fff5a
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/KMACParamsTest.java
@@ -0,0 +1,83 @@
+package org.bouncycastle.asn1.test;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.nist.KMACwithSHAKE128_params;
+import org.bouncycastle.asn1.nist.KMACwithSHAKE256_params;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class KMACParamsTest
+    extends SimpleTest
+{
+    public void performTest()
+        throws Exception
+    {
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(256).getEncoded(), new DERSequence().getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(512).getEncoded(), new DERSequence().getEncoded()));
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(512).getEncoded(), new DERSequence(new ASN1Integer(512)).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(256).getEncoded(), new DERSequence(new ASN1Integer(256)).getEncoded()));
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(512).getEncoded(), KMACwithSHAKE128_params.getInstance(new DERSequence(new ASN1Integer(512))).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(256).getEncoded(), KMACwithSHAKE256_params.getInstance(new DERSequence(new ASN1Integer(256))).getEncoded()));
+
+        byte[] customizationString = Strings.toByteArray("hello, world!");
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(512, customizationString).getEncoded(), new DERSequence(
+            new ASN1Encodable[] { new ASN1Integer(512), new DEROctetString(customizationString) }).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(256, customizationString).getEncoded(), new DERSequence(
+            new ASN1Encodable[] { new ASN1Integer(256), new DEROctetString(customizationString) }).getEncoded()));
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(512, customizationString).getEncoded(),
+            KMACwithSHAKE128_params.getInstance(
+                new DERSequence(new ASN1Encodable[] { new ASN1Integer(512), new DEROctetString(customizationString) })).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(256, customizationString).getEncoded(),
+            KMACwithSHAKE256_params.getInstance(new DERSequence(
+            new ASN1Encodable[] { new ASN1Integer(256), new DEROctetString(customizationString) })).getEncoded()));
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(256, customizationString).getEncoded(), new DERSequence(
+            new ASN1Encodable[] { new DEROctetString(customizationString) }).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(512, customizationString).getEncoded(), new DERSequence(
+            new ASN1Encodable[] { new DEROctetString(customizationString) }).getEncoded()));
+
+        isTrue(Arrays.areEqual(new KMACwithSHAKE128_params(256, customizationString).getEncoded(),
+            KMACwithSHAKE128_params.getInstance(
+                new DERSequence(new ASN1Encodable[] { new DEROctetString(customizationString) })).getEncoded()));
+        isTrue(Arrays.areEqual(new KMACwithSHAKE256_params(512, customizationString).getEncoded(),
+            KMACwithSHAKE256_params.getInstance(new DERSequence(
+            new ASN1Encodable[] { new DEROctetString(customizationString) })).getEncoded()));
+
+        KMACwithSHAKE128_params p128 = new KMACwithSHAKE128_params(256, customizationString);
+        isEquals(256, p128.getOutputLength());
+        isTrue(Arrays.areEqual(customizationString, p128.getCustomizationString()));
+        isTrue(p128 == KMACwithSHAKE128_params.getInstance(p128));
+
+        KMACwithSHAKE256_params p256 = new KMACwithSHAKE256_params(512, customizationString);
+        isEquals(512, p256.getOutputLength());
+        isTrue(Arrays.areEqual(customizationString, p256.getCustomizationString()));
+        isTrue(p256 == KMACwithSHAKE256_params.getInstance(p256));
+
+        p128 = new KMACwithSHAKE128_params(512);
+        isEquals(512, p128.getOutputLength());
+        isTrue(Arrays.areEqual(new byte[0], p128.getCustomizationString()));
+
+        p256 = new KMACwithSHAKE256_params(256);
+        isEquals(256, p256.getOutputLength());
+        isTrue(Arrays.areEqual(new byte[0], p256.getCustomizationString()));
+    }
+
+    public String getName()
+    {
+        return "KMACParams";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new KMACParamsTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/LinkedCertificateTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/LinkedCertificateTest.java
new file mode 100644
index 0000000..340e5f3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/LinkedCertificateTest.java
@@ -0,0 +1,97 @@
+package org.bouncycastle.asn1.test;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.bc.LinkedCertificate;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+
+public class LinkedCertificateTest
+    extends ASN1UnitTest
+{
+    public String getName()
+    {
+        return "LinkedCertificate";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        DigestInfo digInfo = new DigestInfo(
+                                    new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256), new byte[32]);
+        GeneralName certLocation = new GeneralName(GeneralName.uniformResourceIdentifier, "https://www.bouncycastle.org/certs");
+        X500Name certIssuer = null;
+        GeneralNames cACerts = null;
+
+        LinkedCertificate linked = new LinkedCertificate(digInfo, certLocation);
+
+        checkConstruction(linked, digInfo, certLocation, certIssuer, cACerts);
+
+        certIssuer = new X500Name("CN=Test");
+        cACerts = new GeneralNames(new GeneralName(new X500Name("CN=CA Test")));
+
+        linked = new LinkedCertificate(digInfo, certLocation, certIssuer, cACerts);
+
+        checkConstruction(linked, digInfo, certLocation, certIssuer, cACerts);
+
+        linked = LinkedCertificate.getInstance(null);
+
+        if (linked != null)
+        {
+            fail("null getInstance() failed.");
+        }
+
+        try
+        {
+            LinkedCertificate.getInstance(new Object());
+
+            fail("getInstance() failed to detect bad object.");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+    }
+
+    private void checkConstruction(
+        LinkedCertificate linked,
+        DigestInfo digestInfo, GeneralName certLocation, X500Name certIssuer, GeneralNames caCerts)
+        throws IOException
+    {
+        checkValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+
+        linked = LinkedCertificate.getInstance(linked);
+
+        checkValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+
+        ASN1InputStream aIn = new ASN1InputStream(linked.toASN1Primitive().getEncoded());
+
+        ASN1Sequence seq = (ASN1Sequence)aIn.readObject();
+
+        linked = LinkedCertificate.getInstance(seq);
+
+        checkValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+    }
+
+    private void checkValues(
+        LinkedCertificate linked,
+        DigestInfo digestInfo, GeneralName certLocation, X500Name certIssuer, GeneralNames caCerts)
+    {
+        checkMandatoryField("digest", digestInfo, linked.getDigest());
+        checkMandatoryField("certLocatin", certLocation, linked.getCertLocation());
+        checkOptionalField("certIssuer", certIssuer, linked.getCertIssuer());
+        checkOptionalField("caCerts", caCerts, linked.getCACerts());
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new LinkedCertificateTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/LocaleTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/LocaleTest.java
index 89ee7e0..bbae260 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/LocaleTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/LocaleTest.java
@@ -25,16 +25,16 @@
 
         Locale.setDefault(l);
 
-        isTrue(time == new DERUTCTime(timeString).getAdjustedDate().getTime());
-        isTrue(time == new DERGeneralizedTime(longTimeString).getDate().getTime());
+        isTrue("a", time == new DERUTCTime(timeString).getAdjustedDate().getTime());
+        isTrue("b", time == new DERGeneralizedTime(longTimeString).getDate().getTime());
 
-        isTrue(time == new DERUTCTime(new Date(time)).getAdjustedDate().getTime());
-        isTrue(time == new DERGeneralizedTime(new Date(time)).getDate().getTime());
+        isTrue("c", time == new DERUTCTime(new Date(time)).getAdjustedDate().getTime());
+        isTrue("d", time == new DERGeneralizedTime(new Date(time)).getDate().getTime());
 
         Date d = new Date();
 
-        isTrue((d.getTime() - (d.getTime() % 1000)) == new DERUTCTime(d).getAdjustedDate().getTime());
-        isTrue((d.getTime() - (d.getTime() % 1000)) == new DERGeneralizedTime(d).getDate().getTime());
+        isTrue("e", (d.getTime() - (d.getTime() % 1000)) == new DERUTCTime(d).getAdjustedDate().getTime());
+        isTrue("f", (d.getTime() - (d.getTime() % 1000)) == new DERGeneralizedTime(d).getDate().getTime());
     }
 
     public void performTest()
@@ -45,7 +45,11 @@
         Locale list[] = DateFormat.getAvailableLocales();
         for (int i = 0; i != list.length; i++)
         {
-            doTestLocale(list[i]);
+            if (!list[i].getCountry().equals("TH")    // skip Thailand as it appears the JVM is now a day out on this one.
+             && !list[i].getCountry().equals("JP"))   // and it appears the change in era is causing issues here.
+            {
+                doTestLocale(list[i]);
+            }
         }
 
         Locale.setDefault(defLocale);
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/MiscTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/MiscTest.java
index f4dc225..9e17743 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/MiscTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/MiscTest.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.asn1.test;
 
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
@@ -49,7 +48,7 @@
         }
     }
 
-    public void derIntegerTest()
+    public void asn1IntegerTest()
         throws Exception
     {
         try
@@ -105,11 +104,11 @@
         byte[] data = Base64.decode("MA4ECAECAwQFBgcIAgIAgAMCBSAWBWhlbGxvMAoECAECAwQFBgcIFgtodHRwOi8vdGVzdA==");
 
         ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        ASN1OutputStream aOut = new ASN1OutputStream(bOut);
+        ASN1OutputStream aOut = ASN1OutputStream.create(bOut);
 
         for (int i = 0; i != values.length; i++)
         {
-            aOut.writeObject(values[i]);
+            aOut.writeObject(values[i].toASN1Primitive());
         }
 
         if (!areEqual(bOut.toByteArray(), data))
@@ -117,7 +116,7 @@
             fail("Failed data check");
         }
 
-        ASN1InputStream aIn = new ASN1InputStream(bOut.toByteArray());
+        ASN1InputStream aIn = new ASN1InputStream(data);
 
         for (int i = 0; i != values.length; i++)
         {
@@ -134,7 +133,7 @@
         }
 
         shouldFailOnExtraData();
-        derIntegerTest();
+        asn1IntegerTest();
     }
 
     public String getName()
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/OIDTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/OIDTest.java
index fa5aa61..dc4bd69 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/OIDTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/OIDTest.java
@@ -4,10 +4,11 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OutputStream;
-import org.bouncycastle.asn1.DEROutputStream;
+import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
@@ -42,13 +43,8 @@
         {
             fail("oid ID didn't match", o, encO);
         }
-        
-        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-        DEROutputStream          dOut = new DEROutputStream(bOut);
 
-        dOut.writeObject(o);
-
-        byte[]                    bytes = bOut.toByteArray();
+        byte[] bytes = o.getEncoded(ASN1Encoding.DER);
 
         if (bytes.length != enc.length)
         {
@@ -68,17 +64,8 @@
         String  oid)
         throws IOException
     {
-        ASN1ObjectIdentifier     o = new ASN1ObjectIdentifier(oid);
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-        
-        aOut.writeObject(o);
-        
-        ByteArrayInputStream    bIn = new ByteArrayInputStream(bOut.toByteArray());
-        ASN1InputStream         aIn = new ASN1InputStream(bIn);
-        
-        o = (ASN1ObjectIdentifier)aIn.readObject();
-        
+        ASN1ObjectIdentifier o = new ASN1ObjectIdentifier(oid);
+        o = (ASN1ObjectIdentifier)ASN1Primitive.fromByteArray(o.getEncoded());
         if (!o.getId().equals(oid))
         {
             fail("failed oid check for " + oid);
@@ -126,6 +113,10 @@
         
         validOidCheck(PKCSObjectIdentifiers.pkcs_9_at_contentType.getId());
         validOidCheck("0.1");
+        validOidCheck("1.0");
+        validOidCheck("1.0.2");
+        validOidCheck("1.0.20");
+        validOidCheck("1.0.200");
         validOidCheck("1.1.127.32512.8323072.2130706432.545460846592.139637976727552.35747322042253312.9151314442816847872");
         validOidCheck("1.2.123.12345678901.1.1.1");
         validOidCheck("2.25.196556539987194312349856245628873852187.1");
@@ -134,6 +125,11 @@
         invalidOidCheck("1");
         invalidOidCheck("2");
         invalidOidCheck("3.1");
+        invalidOidCheck("0.01");
+        invalidOidCheck("00.1");
+        invalidOidCheck("1.00.2");
+        invalidOidCheck("1.0.02");
+        invalidOidCheck("1.2.00");
         invalidOidCheck("..1");
         invalidOidCheck("192.168.1.1");
         invalidOidCheck(".123452");
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/OctetStringTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/OctetStringTest.java
index 68ed5da..efa29d5 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/OctetStringTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/OctetStringTest.java
@@ -7,11 +7,15 @@
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OctetStringParser;
+import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1SequenceParser;
 import org.bouncycastle.asn1.ASN1StreamParser;
+import org.bouncycastle.asn1.BEROctetString;
 import org.bouncycastle.asn1.BEROctetStringGenerator;
 import org.bouncycastle.asn1.BERSequenceGenerator;
 import org.bouncycastle.asn1.BERTags;
@@ -19,6 +23,7 @@
 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
 import org.bouncycastle.asn1.cms.CompressedDataParser;
 import org.bouncycastle.asn1.cms.ContentInfoParser;
+import org.bouncycastle.util.Arrays;
 
 public class OctetStringTest 
     extends TestCase 
@@ -127,7 +132,28 @@
     
        assertEquals(14, count);
     }
-    
+
+    public void testReadingWritingNestedDirect()
+        throws Exception
+    {
+        ASN1OctetString str = new BEROctetString(
+            new BEROctetString[]{
+                new BEROctetString(new byte[10]),
+                new BEROctetString(new byte[20])
+            });
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ASN1OutputStream aOut = ASN1OutputStream.create(bOut);
+
+        aOut.writeObject(str);
+
+        aOut.close();
+
+        ASN1InputStream aIn = new ASN1InputStream(bOut.toByteArray());
+
+        assertTrue(Arrays.areEqual(new byte[30], ASN1OctetString.getInstance(aIn.readObject()).getOctets()));
+    }
+
     public void testNestedStructure()
         throws Exception
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS10Test.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS10Test.java
index da6571d..4d86016 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS10Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS10Test.java
@@ -1,11 +1,10 @@
 package org.bouncycastle.asn1.test;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.CertificationRequest;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTestResult;
@@ -49,12 +48,7 @@
 
             CertificationRequest    r = new CertificationRequest((ASN1Sequence)aIn.readObject());
 
-            ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
-            DEROutputStream            dOut = new DEROutputStream(bOut);
-
-            dOut.writeObject(r.toASN1Primitive());
-
-            byte[]                    bytes = bOut.toByteArray();
+            byte[] bytes = r.getEncoded(ASN1Encoding.DER);
 
             if (bytes.length != req.length)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS12Test.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS12Test.java
index f533a65..90d81e8 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS12Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/PKCS12Test.java
@@ -1,11 +1,9 @@
 package org.bouncycastle.asn1.test;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1OctetString;
-import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.BEROctetString;
 import org.bouncycastle.asn1.DLSequence;
@@ -164,12 +162,9 @@
 
         b = new SafeBag(PKCSObjectIdentifiers.pkcs8ShroudedKeyBag, encInfo.toASN1Primitive(), b.getBagAttributes());
 
-        ByteArrayOutputStream abOut = new ByteArrayOutputStream();
-        ASN1OutputStream      berOut = new ASN1OutputStream(abOut);
+        byte[] contentOctets = new DLSequence(b).getEncoded();
 
-        berOut.writeObject(new DLSequence(b));
-
-        c[0] = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(abOut.toByteArray()));
+        c[0] = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(contentOctets));
 
         //
         // certificates
@@ -188,12 +183,9 @@
         //
         authSafe = new AuthenticatedSafe(c);
 
-        abOut = new ByteArrayOutputStream();
-        berOut = new ASN1OutputStream(abOut);
+        contentOctets = authSafe.getEncoded();
 
-        berOut.writeObject(authSafe);
-
-        info = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(abOut.toByteArray()));
+        info = new ContentInfo(PKCSObjectIdentifiers.data, new BEROctetString(contentOctets));
 
         mData = new MacData(new DigestInfo(algId, dInfo.getDigest()), salt, itCount);
 
@@ -202,13 +194,8 @@
         //
         // comparison test
         //
-
-        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
-        ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(bag);
-
-        if (!Arrays.areEqual(bOut.toByteArray(), pkcs12))
+        byte[] pfxEncoding = bag.getEncoded();
+        if (!Arrays.areEqual(pfxEncoding, pkcs12))
         {
             fail("failed comparison test");
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/RegressionTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/RegressionTest.java
index 2106cf0..0f2117d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/RegressionTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/RegressionTest.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.asn1.test;
 
+import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
 
 public class RegressionTest
 {
@@ -78,23 +78,14 @@
         new PKIPublicationInfoTest(),
         new CertifiedKeyPairTest(),
         new PrivateKeyInfoTest(),
-        new LocaleTest()
+        new LocaleTest(),
+        new LinkedCertificateTest(),
+        new DLExternalTest(),
+        new KMACParamsTest()
     };
 
-    public static void main(
-        String[]    args)
+    public static void main(String[] args)
     {
-        for (int i = 0; i != tests.length; i++)
-        {
-            TestResult  result = tests[i].perform();
-            
-            if (result.getException() != null)
-            {
-                result.getException().printStackTrace();
-            }
-            
-            System.out.println(result);
-        }
+        SimpleTest.runTests(tests);
     }
 }
-
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/UTCTimeTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/UTCTimeTest.java
index 8067e78..a9c3f80 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/UTCTimeTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/UTCTimeTest.java
@@ -1,11 +1,11 @@
 package org.bouncycastle.asn1.test;
 
-import org.bouncycastle.asn1.DERUTCTime;
-import org.bouncycastle.util.test.SimpleTest;
-
 import java.text.SimpleDateFormat;
 import java.util.SimpleTimeZone;
 
+import org.bouncycastle.asn1.DERUTCTime;
+import org.bouncycastle.util.test.SimpleTest;
+
 /**
  * X.690 test example
  */
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/X500NameTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/X500NameTest.java
index 663e71f..1f8f7ec 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/X500NameTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/X500NameTest.java
@@ -1,15 +1,11 @@
 package org.bouncycastle.asn1.test;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1GeneralizedTime;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1Set;
@@ -43,9 +39,14 @@
        "E=cooke@issl.atl.hp.com,C=US,OU=Hewlett Packard Company (ISSL),CN=Paul A. Cooke",
        "O=Sun Microsystems Inc,CN=store.sun.com",
        "unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com",
-       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET http://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
+       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET https://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
        "O=Bouncy Castle,CN=www.bouncycastle.org\\ ",
        "O=Bouncy Castle,CN=c:\\\\fred\\\\bob",
+       "C=0,O=1,OU=2,T=3,CN=4,SERIALNUMBER=5,STREET=6,SERIALNUMBER=7,L=8,ST=9,SURNAME=10,GIVENNAME=11,INITIALS=12," +
+           "GENERATION=13,UniqueIdentifier=14,BusinessCategory=15,PostalCode=16,DN=17,Pseudonym=18,PlaceOfBirth=19," +
+           "Gender=20,CountryOfCitizenship=21,CountryOfResidence=22,NameAtBirth=23,PostalAddress=24,2.5.4.54=25," +
+           "TelephoneNumber=26,Name=27,E=28,unstructuredName=29,unstructuredAddress=30,E=31,DC=32,UID=33"
+
     };
 
     String[] hexSubjects =
@@ -60,12 +61,10 @@
     {
         return "X500Name";
     }
-    
-    private static X500Name fromBytes(
-        byte[]  bytes) 
-        throws IOException
+
+    private static X500Name fromBytes(byte[] bytes) throws IOException
     {
-        return X500Name.getInstance(new ASN1InputStream(new ByteArrayInputStream(bytes)).readObject());
+        return X500Name.getInstance(ASN1Primitive.fromByteArray(bytes));
     }
 
     private ASN1Encodable createEntryValue(ASN1ObjectIdentifier oid, String value)
@@ -148,7 +147,7 @@
         throws Exception
     {
         ietfUtilsTest();
-
+        
         testEncodingPrintableString(BCStyle.C, "AU");
         testEncodingPrintableString(BCStyle.SERIALNUMBER, "123456");
         testEncodingPrintableString(BCStyle.DN_QUALIFIER, "123456");
@@ -303,9 +302,6 @@
 
         compositeTest();
          */
-        ByteArrayOutputStream bOut;
-        ASN1OutputStream aOut;
-        ASN1InputStream aIn;
        /*
         //
         // getValues test
@@ -329,16 +325,8 @@
         //
         for (int i = 0; i != subjects.length; i++)
         {
-            X500Name    name = new X500Name(subjects[i]);
-
-            bOut = new ByteArrayOutputStream();
-            aOut = new ASN1OutputStream(bOut);
-
-            aOut.writeObject(name);
-
-            aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
-
-            name = X500Name.getInstance(aIn.readObject());
+            X500Name name = new X500Name(subjects[i]);
+            name = X500Name.getInstance(ASN1Primitive.fromByteArray(name.getEncoded()));
             if (!name.toString().equals(subjects[i]))
             {
                 fail("failed regeneration test " + i + " got: " + name.toString() + " expected " + subjects[i]);
@@ -347,16 +335,8 @@
 
         for (int i = 0; i < hexSubjects.length; i += 2)
         {
-            X500Name    name = new X500Name(hexSubjects[i]);
-
-            bOut = new ByteArrayOutputStream();
-            aOut = new ASN1OutputStream(bOut);
-
-            aOut.writeObject(name);
-
-            aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
-
-            name = X500Name.getInstance(aIn.readObject());
+            X500Name name = new X500Name(hexSubjects[i]);
+            name = X500Name.getInstance(ASN1Primitive.fromByteArray(name.getEncoded()));
             if (!name.toString().equals(hexSubjects[i + 1]))
             {
                 fail("failed hex regeneration test " + i + " got: " + name.toString() + " expected " + subjects[i]);
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/X509NameTest.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/X509NameTest.java
index f1ae757..9281fe3 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/X509NameTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/X509NameTest.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.asn1.test;
 
-import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Hashtable;
@@ -41,7 +40,7 @@
        "E=cooke@issl.atl.hp.com,C=US,OU=Hewlett Packard Company (ISSL),CN=Paul A. Cooke",
        "O=Sun Microsystems Inc,CN=store.sun.com",
        "unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com",
-       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET http://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
+       "CN=*.canal-plus.com,OU=Provided by TBS INTERNET https://www.tbs-certificats.com/,OU=\\ CANAL \\+,O=CANAL\\+DISTRIBUTION,L=issy les moulineaux,ST=Hauts de Seine,C=FR",
        "O=Bouncy Castle,CN=www.bouncycastle.org\\ ",
        "O=Bouncy Castle,CN=c:\\\\fred\\\\bob"
     };
@@ -50,12 +49,10 @@
     {
         return "X509Name";
     }
-    
-    private static X509Name fromBytes(
-        byte[]  bytes) 
-        throws IOException
+
+    private static X509Name fromBytes(byte[] bytes) throws IOException
     {
-        return X509Name.getInstance(new ASN1InputStream(new ByteArrayInputStream(bytes)).readObject());
+        return X509Name.getInstance(ASN1Primitive.fromByteArray(bytes));
     }
 
     private ASN1Encodable createEntryValue(ASN1ObjectIdentifier oid, String value)
@@ -324,17 +321,8 @@
         //
         for (int i = 0; i != subjects.length; i++)
         {
-            X509Name    name = new X509Name(subjects[i]);
-
-            bOut = new ByteArrayOutputStream();
-            aOut = new ASN1OutputStream(bOut);
-
-            aOut.writeObject(name);
-
-            aIn = new ASN1InputStream(new ByteArrayInputStream(bOut.toByteArray()));
-
-            name = X509Name.getInstance(aIn.readObject());
-
+            X509Name name = new X509Name(subjects[i]);
+            name = X509Name.getInstance(ASN1Primitive.fromByteArray(name.getEncoded()));
             if (!name.toString().equals(subjects[i]))
             {
                 fail("failed regeneration test " + i + " got " + name.toString());
@@ -623,10 +611,10 @@
         //
         // composite test
         //
-        byte[]  enc = Hex.decode("305e310b300906035504061302415531283026060355040a0c1f546865204c6567696f6e206f662074686520426f756e637920436173746c653125301006035504070c094d656c626f75726e653011060355040b0c0a4173636f742056616c65");
-        ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(enc));
+        byte[] enc = Hex.decode(
+            "305e310b300906035504061302415531283026060355040a0c1f546865204c6567696f6e206f662074686520426f756e637920436173746c653125301006035504070c094d656c626f75726e653011060355040b0c0a4173636f742056616c65");
 
-        X509Name    n = X509Name.getInstance(aIn.readObject());
+        X509Name n = X509Name.getInstance(ASN1Primitive.fromByteArray(enc));
 
         if (!n.toString().equals("C=AU,O=The Legion of the Bouncy Castle,L=Melbourne+OU=Ascot Vale"))
         {
@@ -646,18 +634,13 @@
 
         n = new X509Name("C=AU, O=The Legion of the Bouncy Castle, L=Melbourne + OU=Ascot Vale");
 
-        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-        ASN1OutputStream aOut = new ASN1OutputStream(bOut);
-
-        aOut.writeObject(n);
-
-        byte[]  enc2 = bOut.toByteArray();
+        byte[] enc2 = n.getEncoded();
 
         if (!Arrays.areEqual(enc, enc2))
         {
-            //fail("Failed composite string to encoding test");
+            fail("Failed composite string to encoding test");
         }
-        
+
         //
         // dud name test - handle empty DN without barfing.
         //
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/test/X9Test.java b/bcprov/src/main/java/org/bouncycastle/asn1/test/X9Test.java
index 3fc3e14..64749c1 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/test/X9Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/test/X9Test.java
@@ -74,7 +74,7 @@
 
         X9ECPoint               x9P = new X9ECPoint(ecP.getCurve(), p);
 
-        if (!Arrays.areEqual(p.getOctets(), x9P.getPoint().getEncoded()))
+        if (!Arrays.areEqual(p.getOctets(), x9P.getPointEncoding()))
         {
             fail("point encoding not preserved");
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/tsp/ArchiveTimeStamp.java b/bcprov/src/main/java/org/bouncycastle/asn1/tsp/ArchiveTimeStamp.java
index 1e01a8f..994d6bd 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/tsp/ArchiveTimeStamp.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/tsp/ArchiveTimeStamp.java
@@ -33,10 +33,10 @@
 public class ArchiveTimeStamp
     extends ASN1Object
 {
-    private AlgorithmIdentifier digestAlgorithm;
-    private Attributes attributes;
-    private ASN1Sequence reducedHashTree;
-    private ContentInfo timeStamp;
+    private final AlgorithmIdentifier digestAlgorithm;
+    private final Attributes attributes;
+    private final ASN1Sequence reducedHashTree;
+    private final ContentInfo timeStamp;
 
     /**
      * Return an ArchiveTimestamp from the given object.
@@ -64,9 +64,13 @@
         PartialHashtree[] reducedHashTree,
         ContentInfo timeStamp)
     {
-        this.digestAlgorithm = digestAlgorithm;
-        this.reducedHashTree = new DERSequence(reducedHashTree);
-        this.timeStamp = timeStamp;
+        this(digestAlgorithm, null, reducedHashTree, timeStamp);
+    }
+
+    public ArchiveTimeStamp(
+        ContentInfo timeStamp)
+    {
+        this(null, null, null, timeStamp);
     }
 
     public ArchiveTimeStamp(
@@ -77,13 +81,14 @@
     {
         this.digestAlgorithm = digestAlgorithm;
         this.attributes = attributes;
-        this.reducedHashTree = new DERSequence(reducedHashTree);
-        this.timeStamp = timeStamp;
-    }
-
-    public ArchiveTimeStamp(
-        ContentInfo timeStamp)
-    {
+        if (reducedHashTree != null)
+        {
+            this.reducedHashTree = new DERSequence(reducedHashTree);
+        }
+        else
+        {
+            this.reducedHashTree = null;
+        }
         this.timeStamp = timeStamp;
     }
 
@@ -94,6 +99,9 @@
             throw new IllegalArgumentException("wrong sequence size in constructor: " + sequence.size());
         }
 
+        AlgorithmIdentifier digAlg = null;
+        Attributes attrs = null;
+        ASN1Sequence rHashTree = null;
         for (int i = 0; i < sequence.size() - 1; i++)
         {
             Object obj = sequence.getObjectAt(i);
@@ -105,13 +113,13 @@
                 switch (taggedObject.getTagNo())
                 {
                 case 0:
-                    digestAlgorithm = AlgorithmIdentifier.getInstance(taggedObject, false);
+                    digAlg = AlgorithmIdentifier.getInstance(taggedObject, false);
                     break;
                 case 1:
-                    attributes = Attributes.getInstance(taggedObject, false);
+                    attrs = Attributes.getInstance(taggedObject, false);
                     break;
                 case 2:
-                    reducedHashTree = ASN1Sequence.getInstance(taggedObject, false);
+                    rHashTree = ASN1Sequence.getInstance(taggedObject, false);
                     break;
                 default:
                     throw new IllegalArgumentException("invalid tag no in constructor: "
@@ -120,6 +128,9 @@
             }
         }
 
+        digestAlgorithm = digAlg;
+        attributes = attrs;
+        reducedHashTree = rHashTree;
         timeStamp = ContentInfo.getInstance(sequence.getObjectAt(sequence.size() - 1));
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x500/style/BCStyle.java b/bcprov/src/main/java/org/bouncycastle/asn1/x500/style/BCStyle.java
index 72adf4d..a7be3eb 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x500/style/BCStyle.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x500/style/BCStyle.java
@@ -76,6 +76,8 @@
     public static final ASN1ObjectIdentifier GENERATION = new ASN1ObjectIdentifier("2.5.4.44").intern();
     public static final ASN1ObjectIdentifier UNIQUE_IDENTIFIER = new ASN1ObjectIdentifier("2.5.4.45").intern();
 
+    public static final ASN1ObjectIdentifier DESCRIPTION = new ASN1ObjectIdentifier("2.5.4.13").intern();
+
     /**
      * businessCategory - DirectoryString(SIZE(1..128)
      */
@@ -96,6 +98,7 @@
      */
     public static final ASN1ObjectIdentifier PSEUDONYM = new ASN1ObjectIdentifier("2.5.4.65").intern();
 
+    public static final ASN1ObjectIdentifier ROLE = new ASN1ObjectIdentifier("2.5.4.72").intern();
 
     /**
      * RFC 3039 DateOfBirth - GeneralizedTime - YYYYMMDD000000Z
@@ -213,6 +216,8 @@
         DefaultSymbols.put(GIVENNAME, "GIVENNAME");
         DefaultSymbols.put(INITIALS, "INITIALS");
         DefaultSymbols.put(GENERATION, "GENERATION");
+        DefaultSymbols.put(DESCRIPTION, "DESCRIPTION");
+        DefaultSymbols.put(ROLE, "ROLE");
         DefaultSymbols.put(UnstructuredAddress, "unstructuredAddress");
         DefaultSymbols.put(UnstructuredName, "unstructuredName");
         DefaultSymbols.put(UNIQUE_IDENTIFIER, "UniqueIdentifier");
@@ -249,6 +254,8 @@
         DefaultLookUp.put("givenname", GIVENNAME);
         DefaultLookUp.put("initials", INITIALS);
         DefaultLookUp.put("generation", GENERATION);
+        DefaultLookUp.put("description", DESCRIPTION);
+        DefaultLookUp.put("role", ROLE);
         DefaultLookUp.put("unstructuredaddress", UnstructuredAddress);
         DefaultLookUp.put("unstructuredname", UnstructuredName);
         DefaultLookUp.put("uniqueidentifier", UNIQUE_IDENTIFIER);
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
index be50493..f9a74b1 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityInformationAccess.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.asn1.x509;
 
-import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Primitive;
@@ -28,6 +27,13 @@
 {
     private AccessDescription[]    descriptions;
 
+    private static AccessDescription[] copy(AccessDescription[] descriptions)
+    {
+        AccessDescription[] result = new AccessDescription[descriptions.length];
+        System.arraycopy(descriptions, 0, result, 0, descriptions.length);
+        return result;
+    }
+
     public static AuthorityInformationAccess getInstance(
         Object  obj)
     {
@@ -46,7 +52,7 @@
 
     public static AuthorityInformationAccess fromExtensions(Extensions extensions)
     {
-         return AuthorityInformationAccess.getInstance(extensions.getExtensionParsedValue(Extension.authorityInfoAccess));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.authorityInfoAccess));
     }
 
     private AuthorityInformationAccess(
@@ -58,7 +64,7 @@
         }
 
         descriptions = new AccessDescription[seq.size()];
-        
+
         for (int i = 0; i != seq.size(); i++)
         {
             descriptions[i] = AccessDescription.getInstance(seq.getObjectAt(i));
@@ -68,14 +74,13 @@
     public AuthorityInformationAccess(
         AccessDescription description)
     {
-        this(new AccessDescription[]{ description });
+        this.descriptions = new AccessDescription[]{ description };
     }
 
     public AuthorityInformationAccess(
         AccessDescription[] descriptions)
     {
-        this.descriptions = new AccessDescription[descriptions.length];
-        System.arraycopy(descriptions, 0, this.descriptions, 0, descriptions.length);
+        this.descriptions = copy(descriptions);
     }
 
     /**
@@ -94,9 +99,9 @@
      */
     public AccessDescription[] getAccessDescriptions()
     {
-        return descriptions;
+        return copy(descriptions);
     }
-    
+
     public ASN1Primitive toASN1Primitive()
     {
         return new DERSequence(descriptions);
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
index 0231bb4..06a9537 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java
@@ -34,9 +34,9 @@
 public class AuthorityKeyIdentifier
     extends ASN1Object
 {
-    ASN1OctetString keyidentifier=null;
-    GeneralNames certissuer=null;
-    ASN1Integer certserno=null;
+    ASN1OctetString keyidentifier = null;
+    GeneralNames certissuer = null;
+    ASN1Integer certserno = null;
 
     public static AuthorityKeyIdentifier getInstance(
         ASN1TaggedObject obj,
@@ -62,7 +62,7 @@
 
     public static AuthorityKeyIdentifier fromExtensions(Extensions extensions)
     {
-         return AuthorityKeyIdentifier.getInstance(extensions.getExtensionParsedValue(Extension.authorityKeyIdentifier));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.authorityKeyIdentifier));
     }
 
     protected AuthorityKeyIdentifier(
@@ -72,7 +72,7 @@
 
         while (e.hasMoreElements())
         {
-            ASN1TaggedObject o = DERTaggedObject.getInstance(e.nextElement());
+            ASN1TaggedObject o = ASN1TaggedObject.getInstance(e.nextElement());
 
             switch (o.getTagNo())
             {
@@ -107,13 +107,7 @@
     public AuthorityKeyIdentifier(
         SubjectPublicKeyInfo    spki)
     {
-        Digest  digest = new SHA1Digest();
-        byte[]  resBuf = new byte[digest.getDigestSize()];
-
-        byte[] bytes = spki.getPublicKeyData().getBytes();
-        digest.update(bytes, 0, bytes.length);
-        digest.doFinal(resBuf, 0);
-        this.keyidentifier = new DEROctetString(resBuf);
+        this(spki, null, null);
     }
 
     /**
@@ -134,8 +128,8 @@
         digest.doFinal(resBuf, 0);
 
         this.keyidentifier = new DEROctetString(resBuf);
-        this.certissuer = GeneralNames.getInstance(name.toASN1Primitive());
-        this.certserno = new ASN1Integer(serialNumber);
+        this.certissuer = name;
+        this.certserno = (serialNumber != null) ? new ASN1Integer(serialNumber) : null;
     }
 
     /**
@@ -224,6 +218,8 @@
 
     public String toString()
     {
-        return ("AuthorityKeyIdentifier: KeyID(" + ((keyidentifier != null) ? Hex.toHexString(this.keyidentifier.getOctets()) : "null") + ")");
+        String keyID = (keyidentifier != null) ? Hex.toHexString(keyidentifier.getOctets()) : "null";
+
+        return "AuthorityKeyIdentifier: KeyID(" + keyID + ")";
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/BasicConstraints.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/BasicConstraints.java
index 529597d..fa4c53d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/BasicConstraints.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/BasicConstraints.java
@@ -45,7 +45,7 @@
 
     public static BasicConstraints fromExtensions(Extensions extensions)
     {
-        return BasicConstraints.getInstance(extensions.getExtensionParsedValue(Extension.basicConstraints));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.basicConstraints));
     }
 
     private BasicConstraints(
@@ -152,10 +152,6 @@
     {
         if (pathLenConstraint == null)
         {
-            if (cA == null)
-            {
-                return "BasicConstraints: isCa(false)";
-            }
             return "BasicConstraints: isCa(" + this.isCA() + ")";
         }
         return "BasicConstraints: isCa(" + this.isCA() + "), pathLenConstraint = " + pathLenConstraint.getValue();
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/CRLDistPoint.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/CRLDistPoint.java
index f2f1195..1aa3f38 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/CRLDistPoint.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/CRLDistPoint.java
@@ -19,11 +19,6 @@
         return getInstance(ASN1Sequence.getInstance(obj, explicit));
     }
 
-    public static CRLDistPoint fromExtensions(Extensions extensions)
-    {
-        return CRLDistPoint.getInstance(extensions.getExtensionParsedValue(Extension.cRLDistributionPoints));
-    }
-
     public static CRLDistPoint getInstance(
         Object  obj)
     {
@@ -39,6 +34,11 @@
         return null;
     }
 
+    public static CRLDistPoint fromExtensions(Extensions extensions)
+    {
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.cRLDistributionPoints));
+    }
+
     private CRLDistPoint(
         ASN1Sequence seq)
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/CertificatePolicies.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/CertificatePolicies.java
index 560b464..5b4385a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/CertificatePolicies.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/CertificatePolicies.java
@@ -12,6 +12,13 @@
 {
     private final PolicyInformation[] policyInformation;
 
+    private static PolicyInformation[] copy(PolicyInformation[] policyInfo)
+    {
+        PolicyInformation[] result = new PolicyInformation[policyInfo.length];
+        System.arraycopy(policyInfo, 0, result, 0, policyInfo.length);
+        return result;
+    }
+
     public static CertificatePolicies getInstance(
         Object  obj)
     {
@@ -43,7 +50,7 @@
      */
     public static CertificatePolicies fromExtensions(Extensions extensions)
     {
-        return CertificatePolicies.getInstance(extensions.getExtensionParsedValue(Extension.certificatePolicies));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.certificatePolicies));
     }
 
     /**
@@ -60,7 +67,7 @@
     public CertificatePolicies(
         PolicyInformation[] policyInformation)
     {
-        this.policyInformation = copyPolicyInfo(policyInformation);
+        this.policyInformation = copy(policyInformation);
     }
 
     private CertificatePolicies(
@@ -76,16 +83,7 @@
 
     public PolicyInformation[] getPolicyInformation()
     {
-        return copyPolicyInfo(policyInformation);
-    }
-
-    private PolicyInformation[] copyPolicyInfo(PolicyInformation[] policyInfo)
-    {
-        PolicyInformation[] tmp = new PolicyInformation[policyInfo.length];
-
-        System.arraycopy(policyInfo, 0, tmp, 0, policyInfo.length);
-
-        return tmp;
+        return copy(policyInformation);
     }
 
     public PolicyInformation getPolicyInformation(ASN1ObjectIdentifier policyIdentifier)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
index b39cc32..82aea5a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/ExtendedKeyUsage.java
@@ -68,7 +68,7 @@
      */
     public static ExtendedKeyUsage fromExtensions(Extensions extensions)
     {
-        return ExtendedKeyUsage.getInstance(extensions.getExtensionParsedValue(Extension.extendedKeyUsage));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.extendedKeyUsage));
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/Extensions.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/Extensions.java
index 8294621..0795a08 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/Extensions.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/Extensions.java
@@ -29,6 +29,16 @@
     private Hashtable extensions = new Hashtable();
     private Vector ordering = new Vector();
 
+    public static Extension getExtension(Extensions extensions, ASN1ObjectIdentifier oid)
+    {
+        return null == extensions ? null : extensions.getExtension(oid);
+    }
+
+    public static ASN1Encodable getExtensionParsedValue(Extensions extensions, ASN1ObjectIdentifier oid)
+    {
+        return null == extensions ? null : extensions.getExtensionParsedValue(oid);
+    }
+
     public static Extensions getInstance(
         ASN1TaggedObject obj,
         boolean explicit)
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/GeneralNames.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/GeneralNames.java
index 8be66c1..5ba472b 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/GeneralNames.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/GeneralNames.java
@@ -13,6 +13,13 @@
 {
     private final GeneralName[] names;
 
+    private static GeneralName[] copy(GeneralName[] names)
+    {
+        GeneralName[] result = new GeneralName[names.length];
+        System.arraycopy(names, 0, result, 0, names.length);
+        return result;
+    }
+
     public static GeneralNames getInstance(
         Object  obj)
     {
@@ -38,7 +45,7 @@
 
     public static GeneralNames fromExtensions(Extensions extensions, ASN1ObjectIdentifier extOID)
     {
-        return GeneralNames.getInstance(extensions.getExtensionParsedValue(extOID));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, extOID));
     }
 
     /**
@@ -75,15 +82,6 @@
         return copy(names);
     }
 
-    private GeneralName[] copy(GeneralName[] nms)
-    {
-        GeneralName[] tmp = new GeneralName[nms.length];
-
-        System.arraycopy(nms, 0, tmp, 0, tmp.length);
-
-        return tmp;
-    }
-
     /**
      * Produce an object suitable for an ASN1OutputStream.
      * <pre>
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyPurposeId.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyPurposeId.java
index f545d0b..18e056d 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyPurposeId.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyPurposeId.java
@@ -123,12 +123,12 @@
 
 
     /**
-     * Microsoft Server Gated Crypto (msSGC) see http://www.alvestrand.no/objectid/1.3.6.1.4.1.311.10.3.3.html
+     * Microsoft Server Gated Crypto (msSGC) see https://www.alvestrand.no/objectid/1.3.6.1.4.1.311.10.3.3.html
      */
     public static final KeyPurposeId id_kp_msSGC = new KeyPurposeId(new ASN1ObjectIdentifier("1.3.6.1.4.1.311.10.3.3"));
 
     /**
-     * Netscape Server Gated Crypto (nsSGC) see http://www.alvestrand.no/objectid/2.16.840.1.113730.4.1.html
+     * Netscape Server Gated Crypto (nsSGC) see https://www.alvestrand.no/objectid/2.16.840.1.113730.4.1.html
      */
     public static final KeyPurposeId id_kp_nsSGC = new KeyPurposeId(new ASN1ObjectIdentifier("2.16.840.1.113730.4.1"));
 
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyUsage.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyUsage.java
index d4456b7..8301013 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyUsage.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/KeyUsage.java
@@ -52,7 +52,7 @@
 
     public static KeyUsage fromExtensions(Extensions extensions)
     {
-        return KeyUsage.getInstance(extensions.getExtensionParsedValue(Extension.keyUsage));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.keyUsage));
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PKIXNameConstraintValidator.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PKIXNameConstraintValidator.java
index fbb0967..22bf7ce 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PKIXNameConstraintValidator.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PKIXNameConstraintValidator.java
@@ -67,24 +67,19 @@
             checkPermittedOtherName(permittedSubtreesOtherName, OtherName.getInstance(name.getName()));
             break;
         case GeneralName.rfc822Name:
-            checkPermittedEmail(permittedSubtreesEmail,
-                extractNameAsString(name));
+            checkPermittedEmail(permittedSubtreesEmail, extractNameAsString(name));
             break;
         case GeneralName.dNSName:
-            checkPermittedDNS(permittedSubtreesDNS, DERIA5String.getInstance(
-                name.getName()).getString());
+            checkPermittedDNS(permittedSubtreesDNS, extractNameAsString(name));
             break;
         case GeneralName.directoryName:
             checkPermittedDN(X500Name.getInstance(name.getName()));
             break;
         case GeneralName.uniformResourceIdentifier:
-            checkPermittedURI(permittedSubtreesURI, DERIA5String.getInstance(
-                name.getName()).getString());
+            checkPermittedURI(permittedSubtreesURI, extractNameAsString(name));
             break;
         case GeneralName.iPAddress:
-            byte[] ip = ASN1OctetString.getInstance(name.getName()).getOctets();
-
-            checkPermittedIP(permittedSubtreesIP, ip);
+            checkPermittedIP(permittedSubtreesIP, ASN1OctetString.getInstance(name.getName()).getOctets());
             break;
         default:
             // other tags to be ignored.
@@ -110,20 +105,16 @@
             checkExcludedEmail(excludedSubtreesEmail, extractNameAsString(name));
             break;
         case GeneralName.dNSName:
-            checkExcludedDNS(excludedSubtreesDNS, DERIA5String.getInstance(
-                name.getName()).getString());
+            checkExcludedDNS(excludedSubtreesDNS, extractNameAsString(name));
             break;
         case GeneralName.directoryName:
             checkExcludedDN(X500Name.getInstance(name.getName()));
             break;
         case GeneralName.uniformResourceIdentifier:
-            checkExcludedURI(excludedSubtreesURI, DERIA5String.getInstance(
-                name.getName()).getString());
+            checkExcludedURI(excludedSubtreesURI, extractNameAsString(name));
             break;
         case GeneralName.iPAddress:
-            byte[] ip = ASN1OctetString.getInstance(name.getName()).getOctets();
-
-            checkExcludedIP(excludedSubtreesIP, ip);
+            checkExcludedIP(excludedSubtreesIP, ASN1OctetString.getInstance(name.getName()).getOctets());
             break;
         default:
             // other tags to be ignored.
@@ -254,8 +245,8 @@
                 extractNameAsString(base));
             break;
         case GeneralName.iPAddress:
-            excludedSubtreesIP = unionIP(excludedSubtreesIP, ASN1OctetString
-                .getInstance(base.getName()).getOctets());
+            excludedSubtreesIP = unionIP(excludedSubtreesIP,
+                ASN1OctetString.getInstance(base.getName()).getOctets());
             break;
         default:
             throw new IllegalStateException("Unknown tag encountered: " + base.getTagNo());
@@ -331,7 +322,7 @@
         {
             start = j;
             RDN dnsRdn = RDN.getInstance(dns.getObjectAt(j));
-            if (dnsRdn.equals(subtreeRdnStart))
+            if (IETFUtils.rDNAreEqual(subtreeRdnStart, dnsRdn))
             {
                 break;
             }
@@ -487,7 +478,7 @@
             Iterator it = excluded.iterator();
             while (it.hasNext())
             {
-                ASN1Sequence subtree = (ASN1Sequence)it.next();
+                ASN1Sequence subtree = ASN1Sequence.getInstance(it.next());
 
                 if (withinDNSubtree(dn, subtree))
                 {
@@ -513,13 +504,13 @@
         Set intersect = new HashSet();
         for (Iterator it = otherNames.iterator(); it.hasNext();)
         {
-            Object otName = it.next();
+            OtherName otName1 = OtherName.getInstance(((GeneralSubtree)it.next()).getBase().getName());
 
             if (permitted == null)
             {
-                if (otName != null)
+                if (otName1 != null)
                 {
-                    intersect.add(otName);
+                    intersect.add(otName1);
                 }
             }
             else
@@ -527,16 +518,16 @@
                 Iterator it2 = permitted.iterator();
                 while (it2.hasNext())
                 {
-                    String _permitted = (String)it2.next();
+                    OtherName otName2 = OtherName.getInstance(it2.next());
 
-                    intersectOtherName(otName, _permitted, intersect);
+                    intersectOtherName(otName1, otName2, intersect);
                 }
             }
         }
         return intersect;
     }
 
-    private void intersectOtherName(Object otName1, Object otName2, Set intersect)
+    private void intersectOtherName(OtherName otName1, OtherName otName2, Set intersect)
     {
         if (otName1.equals(otName2))
         {
@@ -865,7 +856,7 @@
 
         while (it.hasNext())
         {
-            OtherName str = ((OtherName)it.next());
+            OtherName str = OtherName.getInstance(it.next());
 
             if (otherNameIsConstrained(name, str))
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyConstraints.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyConstraints.java
index 20cf22c..a6425ec 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyConstraints.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyConstraints.java
@@ -74,7 +74,7 @@
 
     public static PolicyConstraints fromExtensions(Extensions extensions)
     {
-        return PolicyConstraints.getInstance(extensions.getExtensionParsedValue(Extension.policyConstraints));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.policyConstraints));
     }
 
     public BigInteger getRequireExplicitPolicyMapping()
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyMappings.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyMappings.java
index 18f9c61..2ef26bc 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyMappings.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/PolicyMappings.java
@@ -18,7 +18,7 @@
  *      subjectDomainPolicy     CertPolicyId }
  * </pre>
  *
- * @see <a href="http://www.faqs.org/rfc/rfc3280.txt">RFC 3280, section 4.2.1.6</a>
+ * @see <a href="https://www.faqs.org/rfc/rfc3280.txt">RFC 3280, section 4.2.1.6</a>
  */
 public class PolicyMappings
     extends ASN1Object
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
index 52e35a1..84e1366 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java
@@ -42,7 +42,7 @@
 
     public static SubjectKeyIdentifier fromExtensions(Extensions extensions)
     {
-        return SubjectKeyIdentifier.getInstance(extensions.getExtensionParsedValue(Extension.subjectKeyIdentifier));
+        return getInstance(Extensions.getExtensionParsedValue(extensions, Extension.subjectKeyIdentifier));
     }
 
     public SubjectKeyIdentifier(
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java
index 01796ab..6ae198a 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/TBSCertificate.java
@@ -2,13 +2,18 @@
 
 import java.math.BigInteger;
 
+import org.bouncycastle.asn1.ASN1EncodableVector;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Object;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERBitString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERTaggedObject;
 import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.Properties;
 
 /**
  * The TBSCertificate object.
@@ -218,6 +223,69 @@
 
     public ASN1Primitive toASN1Primitive()
     {
-        return seq;
+        if (Properties.getPropertyValue("org.bouncycastle.x509.allow_non-der_tbscert") != null)
+        {
+            if (Properties.isOverrideSet("org.bouncycastle.x509.allow_non-der_tbscert"))
+            {
+                return seq;
+            }
+        }
+        else
+        {
+            return seq;
+        }
+
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        // DEFAULT Zero
+        if (!version.hasValue(BigIntegers.ZERO))
+        {
+            v.add(new DERTaggedObject(true, 0, version));
+        }
+
+        v.add(serialNumber);
+        v.add(signature);
+        v.add(issuer);
+
+        //
+        // before and after dates
+        //
+        {
+            ASN1EncodableVector validity = new ASN1EncodableVector(2);
+            validity.add(startDate);
+            validity.add(endDate);
+
+            v.add(new DERSequence(validity));
+        }
+
+        if (subject != null)
+        {
+            v.add(subject);
+        }
+        else
+        {
+            v.add(new DERSequence());
+        }
+
+        v.add(subjectPublicKeyInfo);
+
+        // Note: implicit tag
+        if (issuerUniqueId != null)
+        {
+            v.add(new DERTaggedObject(false, 1, issuerUniqueId));
+        }
+
+        // Note: implicit tag
+        if (subjectUniqueId != null)
+        {
+            v.add(new DERTaggedObject(false, 2, subjectUniqueId));
+        }
+
+        if (extensions != null)
+        {
+            v.add(new DERTaggedObject(true, 3, extensions));
+        }
+
+        return new DERSequence(v);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509Name.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509Name.java
index 9e49c88..3a4c9e2 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509Name.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509Name.java
@@ -374,7 +374,7 @@
     public static X509Name getInstance(
         Object  obj)
     {
-        if (obj == null || obj instanceof X509Name)
+        if (obj instanceof X509Name)
         {
             return (X509Name)obj;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java b/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
index af218bc..02857c7 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java
@@ -4,7 +4,6 @@
 
 public interface X509ObjectIdentifiers
 {
-    
     /** Subject RDN components: commonName = 2.5.4.3 */
     static final ASN1ObjectIdentifier    commonName              = new ASN1ObjectIdentifier("2.5.4.3").intern();
     /** Subject RDN components: countryName = 2.5.4.6 */
@@ -58,6 +57,34 @@
     static final ASN1ObjectIdentifier  id_pkix = new ASN1ObjectIdentifier("1.3.6.1.5.5.7");
 
     /**
+     *    id-RSASSA-PSS-SHAKE128  OBJECT IDENTIFIER  ::=  { iso(1)
+     *             identified-organization(3) dod(6) internet(1)
+     *             security(5) mechanisms(5) pkix(7) algorithms(6) 30 }
+     */
+    static final ASN1ObjectIdentifier  id_rsassa_pss_shake128   = id_pkix.branch("6.30");
+
+    /**
+     *    id-RSASSA-PSS-SHAKE256  OBJECT IDENTIFIER  ::=  { iso(1)
+     *             identified-organization(3) dod(6) internet(1)
+     *             security(5) mechanisms(5) pkix(7) algorithms(6) 31 }
+     */
+    static final ASN1ObjectIdentifier  id_rsassa_pss_shake256   = id_pkix.branch("6.31");
+
+    /**
+     * id-ecdsa-with-shake128 OBJECT IDENTIFIER  ::=  { iso(1)
+     *        identified-organization(3) dod(6) internet(1)
+     *        security(5) mechanisms(5) pkix(7) algorithms(6) 32 }
+     */
+    static final ASN1ObjectIdentifier  id_ecdsa_with_shake128   = id_pkix.branch("6.32");
+
+    /**
+     * id-ecdsa-with-shake256 OBJECT IDENTIFIER  ::=  { iso(1)
+     *         identified-organization(3) dod(6) internet(1)
+     *         security(5) mechanisms(5) pkix(7) algorithms(6) 33 }
+     */
+    static final ASN1ObjectIdentifier  id_ecdsa_with_shake256   = id_pkix.branch("6.33");
+
+    /**
      * private internet extensions; OID = 1.3.6.1.5.5.7.1
      */
     static final ASN1ObjectIdentifier  id_pe   = id_pkix.branch("1");
diff --git a/bcprov/src/main/java/org/bouncycastle/asn1/x9/ECNamedCurveTable.java b/bcprov/src/main/java/org/bouncycastle/asn1/x9/ECNamedCurveTable.java
index ed54457..6755d8e 100644
--- a/bcprov/src/main/java/org/bouncycastle/asn1/x9/ECNamedCurveTable.java
+++ b/bcprov/src/main/java/org/bouncycastle/asn1/x9/ECNamedCurveTable.java
@@ -5,13 +5,13 @@
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.anssi.ANSSINamedCurves;
+import org.bouncycastle.asn1.cryptlib.CryptlibObjectIdentifiers;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
 import org.bouncycastle.asn1.gm.GMNamedCurves;
 import org.bouncycastle.asn1.nist.NISTNamedCurves;
 import org.bouncycastle.asn1.sec.SECNamedCurves;
 import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
 import org.bouncycastle.crypto.ec.CustomNamedCurves;
-import org.bouncycastle.crypto.params.ECDomainParameters;
 
 /**
  * A general class that reads all X9.62 style EC curve tables.
@@ -52,7 +52,7 @@
 
         if (ecP == null)
         {
-            ecP = fromDomainParameters(ECGOST3410NamedCurves.getByName(name));
+            ecP = ECGOST3410NamedCurves.getByNameX9(name);
         }
 
         if (ecP == null)
@@ -104,6 +104,11 @@
             oid = GMNamedCurves.getOID(name);
         }
 
+        if (oid == null && name.equals("curve25519"))
+        {
+            oid = CryptlibObjectIdentifiers.curvey25519;
+        }
+        
         return oid;
     }
 
@@ -188,7 +193,7 @@
 
         if (ecP == null)
         {
-            ecP = fromDomainParameters(ECGOST3410NamedCurves.getByOID(oid));
+            ecP = ECGOST3410NamedCurves.getByOIDX9(oid);
         }
 
         if (ecP == null)
@@ -228,9 +233,4 @@
             v.addElement(e.nextElement());
         }
     }
-
-    private static X9ECParameters fromDomainParameters(ECDomainParameters dp)
-    {
-        return dp == null ? null : new X9ECParameters(dp.getCurve(), new X9ECPoint(dp.getG(), false), dp.getN(), dp.getH(), dp.getSeed());
-    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/CryptoServicesRegistrar.java b/bcprov/src/main/java/org/bouncycastle/crypto/CryptoServicesRegistrar.java
index d065170..6b28394 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/CryptoServicesRegistrar.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/CryptoServicesRegistrar.java
@@ -129,6 +129,17 @@
     }
 
     /**
+     * Return either the passed-in SecureRandom, or if it is null, then the default source of randomness.
+     *
+     * @param secureRandom the SecureRandom to use if it is not null.
+     * @return the SecureRandom parameter if it is not null, or else the default SecureRandom
+     */
+    public static SecureRandom getSecureRandom(SecureRandom secureRandom)
+    {
+        return null == secureRandom ? getSecureRandom() : secureRandom;
+    }
+
+    /**
      * Set a default secure random to be used where none is otherwise provided.
      *
      * @param secureRandom the SecureRandom to use as the default.
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java
index 9a63522..ac90123 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/KeyGenerationParameters.java
@@ -21,7 +21,7 @@
         SecureRandom    random,
         int             strength)
     {
-        this.random = random;
+        this.random = CryptoServicesRegistrar.getSecureRandom(random);
         this.strength = strength;
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/StagedAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/StagedAgreement.java
new file mode 100644
index 0000000..2ba2048
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/StagedAgreement.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.crypto;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface StagedAgreement
+    extends BasicAgreement
+{
+    AsymmetricKeyParameter calculateStage(CipherParameters pubKey);
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java
index a56d8f1..ce52206 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHStandardGroups.java
@@ -17,19 +17,19 @@
         return new BigInteger(1, Hex.decodeStrict(hex));
     }
 
-    private static DHParameters fromPG(String hexP, String hexG)
+//    private static DHParameters fromPG(String hexP, String hexG)
+//    {
+//        return new DHParameters(fromHex(hexP), fromHex(hexG));
+//    }
+
+    private static DHParameters safePrimeGen2(String hexP)
     {
-        return new DHParameters(fromHex(hexP), fromHex(hexG));
+        return safePrimeGen2(hexP, 0);
     }
 
-    private static DHParameters fromPGQ(String hexP, String hexG, String hexQ)
+    private static DHParameters safePrimeGen2(String hexP, int l)
     {
-        return new DHParameters(fromHex(hexP), fromHex(hexG), fromHex(hexQ));
-    }
-
-    private static DHParameters rfc7919Parameters(String hexP, int l)
-    {
-        // NOTE: All the groups in RFC 7919 use safe primes, i.e. q = (p-1)/2, and generator g = 2
+        // NOTE: A group using a safe prime (i.e. q = (p-1)/2), and generator g = 2
         BigInteger p = fromHex(hexP);
         return new DHParameters(p, TWO, p.shiftRight(1), l);
     }
@@ -40,15 +40,13 @@
     private static final String rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
         + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF";
-    private static final String rfc2409_768_g = "02";
-    public static final DHParameters rfc2409_768 = fromPG(rfc2409_768_p, rfc2409_768_g);
+    public static final DHParameters rfc2409_768 = safePrimeGen2(rfc2409_768_p);
 
     private static final String rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
         + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
         + "FFFFFFFFFFFFFFFF";
-    private static final String rfc2409_1024_g = "02";
-    public static final DHParameters rfc2409_1024 = fromPG(rfc2409_1024_p, rfc2409_1024_g);
+    public static final DHParameters rfc2409_1024 = safePrimeGen2(rfc2409_1024_p);
 
     /*
      * RFC 3526
@@ -58,8 +56,8 @@
         + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
         + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
         + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
-    private static final String rfc3526_1536_g = "02";
-    public static final DHParameters rfc3526_1536 = fromPG(rfc3526_1536_p, rfc3526_1536_g);
+    private static final int rfc3526_1536_l = 200; // RFC3526/RFC7919
+    public static final DHParameters rfc3526_1536 = safePrimeGen2(rfc3526_1536_p, rfc3526_1536_l);
 
     private static final String rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
@@ -67,8 +65,8 @@
         + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
         + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
         + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF";
-    private static final String rfc3526_2048_g = "02";
-    public static final DHParameters rfc3526_2048 = fromPG(rfc3526_2048_p, rfc3526_2048_g);
+    private static final int rfc3526_2048_l = Math.max(225, 112 * 2); // MAX(RFC3526/RFC7919,FIPS)
+    public static final DHParameters rfc3526_2048 = safePrimeGen2(rfc3526_2048_p, rfc3526_2048_l);
 
     private static final String rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
@@ -79,8 +77,8 @@
         + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
         + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
         + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
-    private static final String rfc3526_3072_g = "02";
-    public static final DHParameters rfc3526_3072 = fromPG(rfc3526_3072_p, rfc3526_3072_g);
+    private static final int rfc3526_3072_l = Math.max(275, 128 * 2); // MAX(RFC3526/RFC7919,FIPS)
+    public static final DHParameters rfc3526_3072 = safePrimeGen2(rfc3526_3072_p, rfc3526_3072_l);
 
     private static final String rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
@@ -94,8 +92,8 @@
         + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
         + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
         + "FFFFFFFFFFFFFFFF";
-    private static final String rfc3526_4096_g = "02";
-    public static final DHParameters rfc3526_4096 = fromPG(rfc3526_4096_p, rfc3526_4096_g);
+    private static final int rfc3526_4096_l = Math.max(325, 152 * 2); // MAX(RFC3526/RFC7919,FIPS)
+    public static final DHParameters rfc3526_4096 = safePrimeGen2(rfc3526_4096_p, rfc3526_4096_l);
 
     private static final String rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
         + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
@@ -125,8 +123,8 @@
         + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
         + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
         + "6DCC4024FFFFFFFFFFFFFFFF";
-    private static final String rfc3526_6144_g = "02";
-    public static final DHParameters rfc3526_6144 = fromPG(rfc3526_6144_p, rfc3526_6144_g);
+    private static final int rfc3526_6144_l = Math.max(375, 176 * 2); // MAX(RFC3526/RFC7919,FIPS)
+    public static final DHParameters rfc3526_6144 = safePrimeGen2(rfc3526_6144_p, rfc3526_6144_l);
 
     private static final String rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
         + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
@@ -150,8 +148,8 @@
         + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
         + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
         + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
-    private static final String rfc3526_8192_g = "02";
-    public static final DHParameters rfc3526_8192 = fromPG(rfc3526_8192_p, rfc3526_8192_g);
+    private static final int rfc3526_8192_l = Math.max(400, 200 * 2); // MAX(RFC3526/RFC7919,FIPS)
+    public static final DHParameters rfc3526_8192 = safePrimeGen2(rfc3526_8192_p, rfc3526_8192_l);
 
     /*
      * RFC 4306
@@ -160,66 +158,6 @@
     public static final DHParameters rfc4306_1024 = rfc2409_1024;
 
     /*
-     * RFC 5114
-     */
-    private static final String rfc5114_1024_160_p = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C6"
-        + "9A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C0" + "13ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD70"
-        + "98488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0" + "A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708"
-        + "DF1FB2BC2E4A4371";
-    private static final String rfc5114_1024_160_g = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507F"
-        + "D6406CFF14266D31266FEA1E5C41564B777E690F5504F213" + "160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1"
-        + "909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28A" + "D662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24"
-        + "855E6EEB22B3B2E5";
-    private static final String rfc5114_1024_160_q = "F518AA8781A8DF278ABA4E7D64B7CB9D49462353";
-
-    /**
-     * @deprecated Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf
-     */
-    public static final DHParameters rfc5114_1024_160 = fromPGQ(rfc5114_1024_160_p, rfc5114_1024_160_g,
-        rfc5114_1024_160_q);
-
-    private static final String rfc5114_2048_224_p = "AD107E1E9123A9D0D660FAA79559C51FA20D64E5683B9FD1"
-        + "B54B1597B61D0A75E6FA141DF95A56DBAF9A3C407BA1DF15" + "EB3D688A309C180E1DE6B85A1274A0A66D3F8152AD6AC212"
-        + "9037C9EDEFDA4DF8D91E8FEF55B7394B7AD5B7D0B6C12207" + "C9F98D11ED34DBF6C6BA0B2C8BBC27BE6A00E0A0B9C49708"
-        + "B3BF8A317091883681286130BC8985DB1602E714415D9330" + "278273C7DE31EFDC7310F7121FD5A07415987D9ADC0A486D"
-        + "CDF93ACC44328387315D75E198C641A480CD86A1B9E587E8" + "BE60E69CC928B2B9C52172E413042E9B23F10B0E16E79763"
-        + "C9B53DCF4BA80A29E3FB73C16B8E75B97EF363E2FFA31F71" + "CF9DE5384E71B81C0AC4DFFE0C10E64F";
-    private static final String rfc5114_2048_224_g = "AC4032EF4F2D9AE39DF30B5C8FFDAC506CDEBE7B89998CAF"
-        + "74866A08CFE4FFE3A6824A4E10B9A6F0DD921F01A70C4AFA" + "AB739D7700C29F52C57DB17C620A8652BE5E9001A8D66AD7"
-        + "C17669101999024AF4D027275AC1348BB8A762D0521BC98A" + "E247150422EA1ED409939D54DA7460CDB5F6C6B250717CBE"
-        + "F180EB34118E98D119529A45D6F834566E3025E316A330EF" + "BB77A86F0C1AB15B051AE3D428C8F8ACB70A8137150B8EEB"
-        + "10E183EDD19963DDD9E263E4770589EF6AA21E7F5F2FF381" + "B539CCE3409D13CD566AFBB48D6C019181E1BCFE94B30269"
-        + "EDFE72FE9B6AA4BD7B5A0F1C71CFFF4C19C418E1F6EC0179" + "81BC087F2A7065B384B890D3191F2BFA";
-    private static final String rfc5114_2048_224_q = "801C0D34C58D93FE997177101F80535A4738CEBCBF389A99B36371EB";
-
-    /**
-     * @deprecated Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf
-     */
-    public static final DHParameters rfc5114_2048_224 = fromPGQ(rfc5114_2048_224_p, rfc5114_2048_224_g,
-        rfc5114_2048_224_q);
-
-    private static final String rfc5114_2048_256_p = "87A8E61DB4B6663CFFBBD19C651959998CEEF608660DD0F2"
-        + "5D2CEED4435E3B00E00DF8F1D61957D4FAF7DF4561B2AA30" + "16C3D91134096FAA3BF4296D830E9A7C209E0C6497517ABD"
-        + "5A8A9D306BCF67ED91F9E6725B4758C022E0B1EF4275BF7B" + "6C5BFC11D45F9088B941F54EB1E59BB8BC39A0BF12307F5C"
-        + "4FDB70C581B23F76B63ACAE1CAA6B7902D52526735488A0E" + "F13C6D9A51BFA4AB3AD8347796524D8EF6A167B5A41825D9"
-        + "67E144E5140564251CCACB83E6B486F6B3CA3F7971506026" + "C0B857F689962856DED4010ABD0BE621C3A3960A54E710C3"
-        + "75F26375D7014103A4B54330C198AF126116D2276E11715F" + "693877FAD7EF09CADB094AE91E1A1597";
-    private static final String rfc5114_2048_256_g = "3FB32C9B73134D0B2E77506660EDBD484CA7B18F21EF2054"
-        + "07F4793A1A0BA12510DBC15077BE463FFF4FED4AAC0BB555" + "BE3A6C1B0C6B47B1BC3773BF7E8C6F62901228F8C28CBB18"
-        + "A55AE31341000A650196F931C77A57F2DDF463E5E9EC144B" + "777DE62AAAB8A8628AC376D282D6ED3864E67982428EBC83"
-        + "1D14348F6F2F9193B5045AF2767164E1DFC967C1FB3F2E55" + "A4BD1BFFE83B9C80D052B985D182EA0ADB2A3B7313D3FE14"
-        + "C8484B1E052588B9B7D2BBD2DF016199ECD06E1557CD0915" + "B3353BBB64E0EC377FD028370DF92B52C7891428CDC67EB6"
-        + "184B523D1DB246C32F63078490F00EF8D647D148D4795451" + "5E2327CFEF98C582664B4C0F6CC41659";
-    private static final String rfc5114_2048_256_q = "8CF83642A709A097B447997640129DA299B1A47D1EB3750B"
-        + "A308B0FE64F5FBD3";
-
-    /**
-     * @deprecated Existence of a "hidden SNFS" backdoor cannot be ruled out. see https://eprint.iacr.org/2016/961.pdf
-     */
-    public static final DHParameters rfc5114_2048_256 = fromPGQ(rfc5114_2048_256_p, rfc5114_2048_256_g,
-        rfc5114_2048_256_q);
-
-    /*
      * RFC 5996
      */
     public static final DHParameters rfc5996_768 = rfc4306_768;
@@ -234,7 +172,8 @@
         + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
         + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
         + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B423861285C97FFFFFFFFFFFFFFFF";
-    public static final DHParameters rfc7919_ffdhe2048 = rfc7919Parameters(rfc7919_ffdhe2048_p, 225);
+    private static final int rfc7919_ffdhe2048_l = Math.max(225, 112 * 2); // MAX(RFC7919,FIPS)
+    public static final DHParameters rfc7919_ffdhe2048 = safePrimeGen2(rfc7919_ffdhe2048_p, rfc7919_ffdhe2048_l);
 
     private static final String rfc7919_ffdhe3072_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
         + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
@@ -245,7 +184,8 @@
         + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
         + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
         + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF";
-    public static final DHParameters rfc7919_ffdhe3072 = rfc7919Parameters(rfc7919_ffdhe3072_p, 275);
+    private static final int rfc7919_ffdhe3072_l = Math.max(275, 128 * 2); // MAX(RFC7919,FIPS)
+    public static final DHParameters rfc7919_ffdhe3072 = safePrimeGen2(rfc7919_ffdhe3072_p, rfc7919_ffdhe3072_l);
 
     private static final String rfc7919_ffdhe4096_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
         + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
@@ -259,7 +199,8 @@
         + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
         + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A"
         + "FFFFFFFFFFFFFFFF";
-    public static final DHParameters rfc7919_ffdhe4096 = rfc7919Parameters(rfc7919_ffdhe4096_p, 325);
+    private static final int rfc7919_ffdhe4096_l = Math.max(325, 152 * 2); // MAX(RFC7919,FIPS)
+    public static final DHParameters rfc7919_ffdhe4096 = safePrimeGen2(rfc7919_ffdhe4096_p, rfc7919_ffdhe4096_l);
 
     private static final String rfc7919_ffdhe6144_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
         + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
@@ -278,7 +219,8 @@
         + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A"
         + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1"
         + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF";
-    public static final DHParameters rfc7919_ffdhe6144 = rfc7919Parameters(rfc7919_ffdhe6144_p, 375);
+    private static final int rfc7919_ffdhe6144_l = Math.max(375, 176 * 2); // MAX(RFC7919,FIPS)
+    public static final DHParameters rfc7919_ffdhe6144 = safePrimeGen2(rfc7919_ffdhe6144_p, rfc7919_ffdhe6144_l);
 
     private static final String rfc7919_ffdhe8192_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
         + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
@@ -302,5 +244,6 @@
         + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457"
         + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D"
         + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF";
-    public static final DHParameters rfc7919_ffdhe8192 = rfc7919Parameters(rfc7919_ffdhe8192_p, 400);
+    private static final int rfc7919_ffdhe8192_l = Math.max(400, 200 * 2); // MAX(RFC7919,FIPS)
+    public static final DHParameters rfc7919_ffdhe8192 = safePrimeGen2(rfc7919_ffdhe8192_p, rfc7919_ffdhe8192_l);
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHUnifiedAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHUnifiedAgreement.java
index 97172f6..91fd39d 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHUnifiedAgreement.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/DHUnifiedAgreement.java
@@ -5,7 +5,6 @@
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.params.DHUPrivateParameters;
 import org.bouncycastle.crypto.params.DHUPublicParameters;
-import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.BigIntegers;
 
 /**
@@ -41,8 +40,10 @@
 
         BigInteger eComp = eAgree.calculateAgreement(pubParams.getEphemeralPublicKey());
 
-        return Arrays.concatenate(
-            BigIntegers.asUnsignedByteArray(this.getFieldSize(), eComp),
-            BigIntegers.asUnsignedByteArray(this.getFieldSize(), sComp));
+        int fieldSize = getFieldSize();
+        byte[] result = new byte[fieldSize * 2];
+        BigIntegers.asUnsignedByteArray(eComp, result, 0, fieldSize);
+        BigIntegers.asUnsignedByteArray(sComp, result, fieldSize, fieldSize);
+        return result;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCStagedAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCStagedAgreement.java
new file mode 100644
index 0000000..0e65ddb
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCStagedAgreement.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.crypto.agreement;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.StagedAgreement;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.math.ec.ECAlgorithms;
+import org.bouncycastle.math.ec.ECPoint;
+
+public class ECDHCStagedAgreement
+    implements StagedAgreement
+{
+    ECPrivateKeyParameters key;
+
+    public void init(
+        CipherParameters key)
+    {
+        this.key = (ECPrivateKeyParameters)key;
+    }
+
+    public int getFieldSize()
+    {
+        return (key.getParameters().getCurve().getFieldSize() + 7) / 8;
+    }
+
+    public AsymmetricKeyParameter calculateStage(
+        CipherParameters pubKey)
+    {
+        ECPoint P = calculateNextPoint((ECPublicKeyParameters)pubKey);
+
+        return new ECPublicKeyParameters(P, key.getParameters());
+    }
+
+    public BigInteger calculateAgreement(
+        CipherParameters pubKey)
+    {
+        ECPoint P = calculateNextPoint((ECPublicKeyParameters)pubKey);
+
+        return P.getAffineXCoord().toBigInteger();
+    }
+
+    private ECPoint calculateNextPoint(ECPublicKeyParameters pubKey)
+    {
+        ECPublicKeyParameters pub = pubKey;
+        ECDomainParameters params = key.getParameters();
+        if (!params.equals(pub.getParameters()))
+        {
+            throw new IllegalStateException("ECDHC public key has wrong domain parameters");
+        }
+
+        BigInteger hd = params.getH().multiply(key.getD()).mod(params.getN());
+
+        // Always perform calculations on the exact curve specified by our private key's parameters
+        ECPoint pubPoint = ECAlgorithms.cleanPoint(params.getCurve(), pub.getQ());
+        if (pubPoint.isInfinity())
+        {
+            throw new IllegalStateException("Infinity is not a valid public key for ECDHC");
+        }
+
+        ECPoint P = pubPoint.multiply(hd).normalize();
+
+        if (P.isInfinity())
+        {
+            throw new IllegalStateException("Infinity is not a valid agreement value for ECDHC");
+        }
+
+        return P;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCUnifiedAgreement.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCUnifiedAgreement.java
index 463c9a4..edc76fd 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCUnifiedAgreement.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/ECDHCUnifiedAgreement.java
@@ -5,7 +5,6 @@
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.params.ECDHUPrivateParameters;
 import org.bouncycastle.crypto.params.ECDHUPublicParameters;
-import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.BigIntegers;
 
 /**
@@ -41,8 +40,10 @@
 
         BigInteger eComp = eAgree.calculateAgreement(pubParams.getEphemeralPublicKey());
 
-        return Arrays.concatenate(
-            BigIntegers.asUnsignedByteArray(this.getFieldSize(), eComp),
-            BigIntegers.asUnsignedByteArray(this.getFieldSize(), sComp));
+        int fieldSize = getFieldSize();
+        byte[] result = new byte[fieldSize * 2];
+        BigIntegers.asUnsignedByteArray(eComp, result, 0, fieldSize);
+        BigIntegers.asUnsignedByteArray(sComp, result, fieldSize, fieldSize);
+        return result;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
index aca5029..946365e 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEParticipant.java
@@ -49,7 +49,7 @@
  * These are the trivial techniques to optimize the communication.
  * <p>
  * The key confirmation process is implemented as specified in
- * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+ * <a href="https://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
  * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
  * <p>
  * This class is stateful and NOT threadsafe.
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
index ad2c6ae..c3c9561 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroup.java
@@ -10,7 +10,7 @@
  * <p>
  * See {@link JPAKEPrimeOrderGroups} for convenient standard groups.
  * <p>
- * NIST <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
+ * NIST <a href="https://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">publishes</a>
  * many groups that can be used for the desired level of security.
  */
 public class JPAKEPrimeOrderGroup
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java
index 2fb1861..c80ee21 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEPrimeOrderGroups.java
@@ -11,7 +11,7 @@
  * <p>
  * The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB),
  * and from the prime order groups
- * <a href="http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">published by NIST</a>.
+ * <a href="https://csrc.nist.gov/groups/ST/toolkit/documents/Examples/DSA2_All.pdf">published by NIST</a>.
  */
 public class JPAKEPrimeOrderGroups
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java
index 0a6c5fe..6a487ea 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/agreement/jpake/JPAKEUtil.java
@@ -317,7 +317,7 @@
 
     /**
      * Calculates the MacTag (to be used for key confirmation), as defined by
-     * <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
+     * <a href="https://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
      * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
      * <pre>
      * MacTag = HMAC(MacKey, MacLen, MacData)
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java
index 5e8af99..1a3a672 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/Blake2bDigest.java
@@ -25,6 +25,7 @@
 
 import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Longs;
 import org.bouncycastle.util.Pack;
 
 
@@ -482,18 +483,13 @@
     {
 
         internalState[posA] = internalState[posA] + internalState[posB] + m1;
-        internalState[posD] = rotr64(internalState[posD] ^ internalState[posA], 32);
+        internalState[posD] = Longs.rotateRight(internalState[posD] ^ internalState[posA], 32);
         internalState[posC] = internalState[posC] + internalState[posD];
-        internalState[posB] = rotr64(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
+        internalState[posB] = Longs.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
         internalState[posA] = internalState[posA] + internalState[posB] + m2;
-        internalState[posD] = rotr64(internalState[posD] ^ internalState[posA], 16);
+        internalState[posD] = Longs.rotateRight(internalState[posD] ^ internalState[posA], 16);
         internalState[posC] = internalState[posC] + internalState[posD];
-        internalState[posB] = rotr64(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
-    }
-
-    private static long rotr64(long x, int rot)
-    {
-        return x >>> rot | (x << (64 - rot));
+        internalState[posB] = Longs.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/CSHAKEDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/CSHAKEDigest.java
index 7629497..286dcb8 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/CSHAKEDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/CSHAKEDigest.java
@@ -15,8 +15,8 @@
      * Base constructor.
      *
      * @param bitLength bit length of the underlying SHAKE function, 128 or 256.
-     * @param N the function name string, note this is reserved for use by NIST. Avoid using it if not required.
-     * @param S the customization string - available for local use.
+     * @param N         the function name string, note this is reserved for use by NIST. Avoid using it if not required.
+     * @param S         the customization string - available for local use.
      */
     public CSHAKEDigest(int bitLength, byte[] N, byte[] S)
     {
@@ -28,59 +28,49 @@
         }
         else
         {
-            diff = Arrays.concatenate(leftEncode(rate / 8), encodeString(N), encodeString(S));
+            diff = Arrays.concatenate(XofUtils.leftEncode(rate / 8), encodeString(N), encodeString(S));
             diffPadAndAbsorb();
         }
     }
 
+    // bytepad in SP 800-185
     private void diffPadAndAbsorb()
     {
         int blockSize = rate / 8;
         absorb(diff, 0, diff.length);
 
-        int required = blockSize - (diff.length % blockSize);
+        int delta = diff.length % blockSize;
 
-        while (required > padding.length)
+        // only add padding if needed
+        if (delta != 0)
         {
-            absorb(padding, 0, padding.length);
-            required -= padding.length;
+            int required = blockSize - delta;
+
+            while (required > padding.length)
+            {
+                absorb(padding, 0, padding.length);
+                required -= padding.length;
+            }
+
+            absorb(padding, 0, required);
         }
-        
-        absorb(padding, 0, required);
     }
 
     private byte[] encodeString(byte[] str)
     {
         if (str == null || str.length == 0)
         {
-            return leftEncode(0);
+            return XofUtils.leftEncode(0);
         }
 
-        return Arrays.concatenate(leftEncode(str.length * 8L), str);
+        return Arrays.concatenate(XofUtils.leftEncode(str.length * 8L), str);
     }
-    
-    private static byte[] leftEncode(long strLen)
+
+    public String getAlgorithmName()
     {
-    	byte n = 1;
-
-        long v = strLen;
-    	while ((v >>= 8) != 0)
-        {
-    		n++;
-    	}
-
-        byte[] b = new byte[n + 1];
-
-    	b[0] = n;
-  
-    	for (int i = 1; i <= n; i++)
-    	{
-    		b[i] = (byte)(strLen >> (8 * (n - i)));
-    	}
- 
-    	return b;
+        return "CSHAKE" + fixedOutputLength;
     }
-    
+
     public int doOutput(byte[] out, int outOff, int outLen)
     {
         if (diff != null)
@@ -103,7 +93,7 @@
     public void reset()
     {
         super.reset();
-        
+
         if (diff != null)
         {
             diffPadAndAbsorb();
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java
index e14c5c4..1292cd7 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java
@@ -3,6 +3,7 @@
 import org.bouncycastle.crypto.ExtendedDigest;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Pack;
+import org.bouncycastle.util.encoders.Hex;
 
 /**
  * implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
@@ -12,12 +13,12 @@
 public class KeccakDigest
     implements ExtendedDigest
 {
-    private static long[] KeccakRoundConstants = new long[]{ 0x0000000000000001L, 0x0000000000008082L,
+    private static long[] KeccakRoundConstants = new long[]{0x0000000000000001L, 0x0000000000008082L,
         0x800000000000808aL, 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L,
         0x8000000000008009L, 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL,
         0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L,
         0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, 0x8000000000008080L,
-        0x0000000080000001L, 0x8000000080008008L };
+        0x0000000080000001L, 0x8000000080008008L};
 
     protected long[] state = new long[25];
     protected byte[] dataQueue = new byte[192];
@@ -36,6 +37,7 @@
         init(bitLength);
     }
 
+
     public KeccakDigest(KeccakDigest source)
     {
         System.arraycopy(source.state, 0, this.state, 0, source.state.length);
@@ -58,7 +60,7 @@
 
     public void update(byte in)
     {
-        absorb(new byte[]{ in }, 0, 1);
+        absorb(in);
     }
 
     public void update(byte[] in, int inOff, int len)
@@ -142,6 +144,25 @@
         this.fixedOutputLength = (1600 - rate) / 2;
     }
 
+    protected void absorb(byte data)
+    {
+        if ((bitsInQueue % 8) != 0)
+        {
+            throw new IllegalStateException("attempt to absorb with odd length queue");
+        }
+        if (squeezing)
+        {
+            throw new IllegalStateException("attempt to absorb while squeezing");
+        }
+
+        dataQueue[bitsInQueue >>> 3] = data;
+        if ((bitsInQueue += 8) == rate)
+        {
+            KeccakAbsorb(dataQueue, 0);
+            bitsInQueue = 0;
+        }
+    }
+
     protected void absorb(byte[] data, int off, int len)
     {
         if ((bitsInQueue % 8) != 0)
@@ -153,38 +174,34 @@
             throw new IllegalStateException("attempt to absorb while squeezing");
         }
 
-        int bytesInQueue = bitsInQueue >> 3;
-        int rateBytes = rate >> 3;
+        int bytesInQueue = bitsInQueue >>> 3;
+        int rateBytes = rate >>> 3;
 
-        int count = 0;
-        while (count < len)
+        int available = rateBytes - bytesInQueue;
+        if (len < available)
         {
-            if (bytesInQueue == 0 && count <= (len - rateBytes))
-            {
-                do
-                {
-                    KeccakAbsorb(data, off + count);
-                    count += rateBytes;
-                }
-                while (count <= (len - rateBytes));
-            }
-            else
-            {
-                int partialBlock = Math.min(rateBytes - bytesInQueue, len - count);
-                System.arraycopy(data, off + count, dataQueue, bytesInQueue, partialBlock);
-
-                bytesInQueue += partialBlock;
-                count += partialBlock;
-
-                if (bytesInQueue == rateBytes)
-                {
-                    KeccakAbsorb(dataQueue, 0);
-                    bytesInQueue = 0;
-                }
-            }
+            System.arraycopy(data, off, dataQueue, bytesInQueue, len);
+            this.bitsInQueue += len << 3;
+            return;
         }
 
-        bitsInQueue = bytesInQueue << 3;
+        int count = 0;
+        if (bytesInQueue > 0)
+        {
+            System.arraycopy(data, off, dataQueue, bytesInQueue, available);
+            count += available;
+            KeccakAbsorb(dataQueue, 0);
+        }
+
+        int remaining;
+        while ((remaining = (len - count)) >= rateBytes)
+        {
+            KeccakAbsorb(data, off + count);
+            count += rateBytes;
+        }
+
+        System.arraycopy(data, off + count, dataQueue, 0, remaining);
+        this.bitsInQueue = remaining << 3;
     }
 
     protected void absorbBits(int data, int bits)
@@ -203,43 +220,57 @@
         }
 
         int mask = (1 << bits) - 1;
-        dataQueue[bitsInQueue >> 3] = (byte)(data & mask);
+        dataQueue[bitsInQueue >>> 3] = (byte)(data & mask);
 
         // NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work
         bitsInQueue += bits;
     }
 
+
+    protected byte[] dumpState()
+    {
+        byte[] out = new byte[state.length * 8];
+        int p = 0;
+        for (int i = 0; i != state.length; i++)
+        {
+            Pack.longToLittleEndian(state[i], out, p);
+            p += 8;
+        }
+
+        return out;
+    }
+
+
     private void padAndSwitchToSqueezingPhase()
     {
-        dataQueue[bitsInQueue >> 3] |= (byte)(1L << (bitsInQueue & 7));
+        dataQueue[bitsInQueue >>> 3] |= (byte)(1 << (bitsInQueue & 7));
 
         if (++bitsInQueue == rate)
         {
             KeccakAbsorb(dataQueue, 0);
-            bitsInQueue = 0;
         }
-
+        else
         {
-            int full = bitsInQueue >> 6, partial = bitsInQueue & 63;
+            int full = bitsInQueue >>> 6, partial = bitsInQueue & 63;
             int off = 0;
             for (int i = 0; i < full; ++i)
             {
                 state[i] ^= Pack.littleEndianToLong(dataQueue, off);
                 off += 8;
             }
+
+            byte[] z = dumpState();
+
             if (partial > 0)
             {
                 long mask = (1L << partial) - 1L;
                 state[full] ^= Pack.littleEndianToLong(dataQueue, off) & mask;
             }
-            state[(rate - 1) >> 6] ^= (1L << 63);
         }
 
-        KeccakPermutation();
+        state[(rate - 1) >>> 6] ^= (1L << 63);
 
-        KeccakExtract();
-        bitsInQueue = rate;
-
+        bitsInQueue = 0;
         squeezing = true;
     }
 
@@ -249,6 +280,9 @@
         {
             padAndSwitchToSqueezingPhase();
         }
+
+        byte[] z = dumpState();
+
         if ((outputLength % 8) != 0)
         {
             throw new IllegalStateException("outputLength not a multiple of 8");
@@ -259,40 +293,51 @@
         {
             if (bitsInQueue == 0)
             {
-                KeccakPermutation();
                 KeccakExtract();
-                bitsInQueue = rate;
             }
             int partialBlock = (int)Math.min((long)bitsInQueue, outputLength - i);
             System.arraycopy(dataQueue, (rate - bitsInQueue) / 8, output, offset + (int)(i / 8), partialBlock / 8);
             bitsInQueue -= partialBlock;
             i += partialBlock;
         }
+
+        z = dumpState();
+
     }
 
     private void KeccakAbsorb(byte[] data, int off)
     {
-        int count = rate >> 6;
+//        assert 0 == bitsInQueue || (dataQueue == data && 0 == off);
+
+        int count = rate >>> 6;
         for (int i = 0; i < count; ++i)
         {
             state[i] ^= Pack.littleEndianToLong(data, off);
             off += 8;
         }
-
+        String z = Hex.toHexString(dumpState()).toLowerCase();
         KeccakPermutation();
     }
 
     private void KeccakExtract()
     {
-        Pack.longToLittleEndian(state, 0, rate >> 6, dataQueue, 0);
+//        assert 0 == bitsInQueue;
+
+        KeccakPermutation();
+
+        byte[] z = dumpState();
+
+        Pack.longToLittleEndian(state, 0, rate >>> 6, dataQueue, 0);
+
+        this.bitsInQueue = rate;
     }
 
     private void KeccakPermutation()
     {
         long[] A = state;
 
-        long a00 = A[ 0], a01 = A[ 1], a02 = A[ 2], a03 = A[ 3], a04 = A[ 4];
-        long a05 = A[ 5], a06 = A[ 6], a07 = A[ 7], a08 = A[ 8], a09 = A[ 9];
+        long a00 = A[0], a01 = A[1], a02 = A[2], a03 = A[3], a04 = A[4];
+        long a05 = A[5], a06 = A[6], a07 = A[7], a08 = A[8], a09 = A[9];
         long a10 = A[10], a11 = A[11], a12 = A[12], a13 = A[13], a14 = A[14];
         long a15 = A[15], a16 = A[16], a17 = A[17], a18 = A[18], a19 = A[19];
         long a20 = A[20], a21 = A[21], a22 = A[22], a23 = A[23], a24 = A[24];
@@ -312,37 +357,57 @@
             long d4 = (c4 << 1 | c4 >>> -1) ^ c2;
             long d0 = (c0 << 1 | c0 >>> -1) ^ c3;
 
-            a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1;
-            a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2;
-            a02 ^= d3; a07 ^= d3; a12 ^= d3; a17 ^= d3; a22 ^= d3;
-            a03 ^= d4; a08 ^= d4; a13 ^= d4; a18 ^= d4; a23 ^= d4;
-            a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0;
+            a00 ^= d1;
+            a05 ^= d1;
+            a10 ^= d1;
+            a15 ^= d1;
+            a20 ^= d1;
+            a01 ^= d2;
+            a06 ^= d2;
+            a11 ^= d2;
+            a16 ^= d2;
+            a21 ^= d2;
+            a02 ^= d3;
+            a07 ^= d3;
+            a12 ^= d3;
+            a17 ^= d3;
+            a22 ^= d3;
+            a03 ^= d4;
+            a08 ^= d4;
+            a13 ^= d4;
+            a18 ^= d4;
+            a23 ^= d4;
+            a04 ^= d0;
+            a09 ^= d0;
+            a14 ^= d0;
+            a19 ^= d0;
+            a24 ^= d0;
 
             // rho/pi
-            c1  = a01 <<  1 | a01 >>> 63;
+            c1 = a01 << 1 | a01 >>> 63;
             a01 = a06 << 44 | a06 >>> 20;
             a06 = a09 << 20 | a09 >>> 44;
-            a09 = a22 << 61 | a22 >>>  3;
+            a09 = a22 << 61 | a22 >>> 3;
             a22 = a14 << 39 | a14 >>> 25;
             a14 = a20 << 18 | a20 >>> 46;
-            a20 = a02 << 62 | a02 >>>  2;
+            a20 = a02 << 62 | a02 >>> 2;
             a02 = a12 << 43 | a12 >>> 21;
             a12 = a13 << 25 | a13 >>> 39;
-            a13 = a19 <<  8 | a19 >>> 56;
-            a19 = a23 << 56 | a23 >>>  8;
+            a13 = a19 << 8 | a19 >>> 56;
+            a19 = a23 << 56 | a23 >>> 8;
             a23 = a15 << 41 | a15 >>> 23;
             a15 = a04 << 27 | a04 >>> 37;
             a04 = a24 << 14 | a24 >>> 50;
-            a24 = a21 <<  2 | a21 >>> 62;
-            a21 = a08 << 55 | a08 >>>  9;
+            a24 = a21 << 2 | a21 >>> 62;
+            a21 = a08 << 55 | a08 >>> 9;
             a08 = a16 << 45 | a16 >>> 19;
             a16 = a05 << 36 | a05 >>> 28;
             a05 = a03 << 28 | a03 >>> 36;
             a03 = a18 << 21 | a18 >>> 43;
             a18 = a17 << 15 | a17 >>> 49;
             a17 = a11 << 10 | a11 >>> 54;
-            a11 = a07 <<  6 | a07 >>> 58;
-            a07 = a10 <<  3 | a10 >>> 61;
+            a11 = a07 << 6 | a07 >>> 58;
+            a07 = a10 << 3 | a10 >>> 61;
             a10 = c1;
 
             // chi
@@ -389,11 +454,31 @@
             // iota
             a00 ^= KeccakRoundConstants[i];
         }
-        
-        A[ 0] = a00; A[ 1] = a01; A[ 2] = a02; A[ 3] = a03; A[ 4] = a04;
-        A[ 5] = a05; A[ 6] = a06; A[ 7] = a07; A[ 8] = a08; A[ 9] = a09;
-        A[10] = a10; A[11] = a11; A[12] = a12; A[13] = a13; A[14] = a14;
-        A[15] = a15; A[16] = a16; A[17] = a17; A[18] = a18; A[19] = a19;
-        A[20] = a20; A[21] = a21; A[22] = a22; A[23] = a23; A[24] = a24;
+
+        A[0] = a00;
+        A[1] = a01;
+        A[2] = a02;
+        A[3] = a03;
+        A[4] = a04;
+        A[5] = a05;
+        A[6] = a06;
+        A[7] = a07;
+        A[8] = a08;
+        A[9] = a09;
+        A[10] = a10;
+        A[11] = a11;
+        A[12] = a12;
+        A[13] = a13;
+        A[14] = a14;
+        A[15] = a15;
+        A[16] = a16;
+        A[17] = a17;
+        A[18] = a18;
+        A[19] = a19;
+        A[20] = a20;
+        A[21] = a21;
+        A[22] = a22;
+        A[23] = a23;
+        A[24] = a24;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
index 20c81e6..f79047a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/RIPEMD160Digest.java
@@ -5,7 +5,7 @@
 
 /**
  * implementation of RIPEMD see,
- * http://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html
+ * https://www.esat.kuleuven.ac.be/~bosselae/ripemd160.html
  */
 public class RIPEMD160Digest
     extends GeneralDigest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java
index 85537d0..c70e140 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHA3Digest.java
@@ -64,7 +64,7 @@
 
         if (finalBits >= 8)
         {
-            absorb(new byte[]{ (byte)finalInput }, 0, 1);
+            absorb((byte)finalInput);
             finalBits -= 8;
             finalInput >>>= 8;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java
index 67375c8..82b46b6 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SHAKEDigest.java
@@ -92,7 +92,7 @@
 
         if (finalBits >= 8)
         {
-            absorb(new byte[]{ (byte)finalInput }, 0, 1);
+            absorb((byte)finalInput);
             finalBits -= 8;
             finalInput >>>= 8;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java
index 4a527c1..a1b1034 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/SM3Digest.java
@@ -5,7 +5,7 @@
 
 /**
  * Implementation of Chinese SM3 digest as described at
- * http://tools.ietf.org/html/draft-shen-sm3-hash-01
+ * https://tools.ietf.org/html/draft-shen-sm3-hash-01
  * and at .... ( Chinese PDF )
  * <p>
  * The specification says "process a bit stream",
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java
index 2899e30..e98af42 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/TigerDigest.java
@@ -5,8 +5,8 @@
 
 /**
  * implementation of Tiger based on:
- * <a href="http://www.cs.technion.ac.il/~biham/Reports/Tiger">
- *  http://www.cs.technion.ac.il/~biham/Reports/Tiger</a>
+ * <a href="https://www.cs.technion.ac.il/~biham/Reports/Tiger">
+ *  https://www.cs.technion.ac.il/~biham/Reports/Tiger</a>
  */
 public class TigerDigest
     implements ExtendedDigest, Memoable
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/digests/XofUtils.java b/bcprov/src/main/java/org/bouncycastle/crypto/digests/XofUtils.java
new file mode 100644
index 0000000..6e06a47
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/digests/XofUtils.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.crypto.digests;
+
+public class XofUtils
+{
+    public static byte[] leftEncode(long strLen)
+    {
+    	byte n = 1;
+
+        long v = strLen;
+    	while ((v >>= 8) != 0)
+        {
+    		n++;
+    	}
+
+        byte[] b = new byte[n + 1];
+
+    	b[0] = n;
+
+    	for (int i = 1; i <= n; i++)
+    	{
+    		b[i] = (byte)(strLen >> (8 * (n - i)));
+    	}
+
+    	return b;
+    }
+
+    public static byte[] rightEncode(long strLen)
+    {
+        byte n = 1;
+
+        long v = strLen;
+        while ((v >>= 8) != 0)
+        {
+            n++;
+        }
+
+        byte[] b = new byte[n + 1];
+
+        b[n] = n;
+
+        for (int i = 0; i < n; i++)
+        {
+            b[i] = (byte)(strLen >> (8 * (n - i - 1)));
+        }
+
+        return b;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java b/bcprov/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java
index a0d9361..cad6932 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/encodings/OAEPEncoding.java
@@ -297,6 +297,7 @@
         byte[]  output = new byte[block.length - start];
 
         System.arraycopy(block, start, output, 0, output.length);
+        Arrays.fill(block, (byte)0);
 
         return output;
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/bcprov/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
index cfd6dcf..02a1ff1 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/encodings/PKCS1Encoding.java
@@ -1,7 +1,5 @@
 package org.bouncycastle.crypto.encodings;
 
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.security.SecureRandom;
 
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
@@ -11,6 +9,7 @@
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Properties;
 
 /**
  * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this
@@ -95,28 +94,12 @@
     //
     private boolean useStrict()
     {
-        // required if security manager has been installed.
-        String strict = (String)AccessController.doPrivileged(new PrivilegedAction()
+        if (Properties.isOverrideSetTo(NOT_STRICT_LENGTH_ENABLED_PROPERTY, true))
         {
-            public Object run()
-            {
-                return System.getProperty(STRICT_LENGTH_ENABLED_PROPERTY);
-            }
-        });
-        String notStrict = (String)AccessController.doPrivileged(new PrivilegedAction()
-        {
-            public Object run()
-            {
-                return System.getProperty(NOT_STRICT_LENGTH_ENABLED_PROPERTY);
-            }
-        });
-
-        if (notStrict != null)
-        {
-            return !notStrict.equals("true");
+            return false;
         }
 
-        return strict == null || strict.equals("true");
+        return !Properties.isOverrideSetTo(STRICT_LENGTH_ENABLED_PROPERTY, false);
     }
 
     public AsymmetricBlockCipher getUnderlyingCipher()
@@ -269,7 +252,7 @@
 		 * Now the padding check, check for no 0 byte in the padding
 		 */
         int plen = encoded.length - (
-            pLen /* Lenght of the PMS */
+            pLen /* Length of the PMS */
                 + 1 /* Final 0-byte before PMS */
         );
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java
index 021b0f7..0b491ab 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESEngine.java
@@ -11,7 +11,7 @@
 /**
  * an implementation of the AES (Rijndael), from FIPS-197.
  * <p>
- * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>.
+ * For further details see: <a href="https://csrc.nist.gov/encryption/aes/">https://csrc.nist.gov/encryption/aes/</a>.
  *
  * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
  * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
@@ -296,98 +296,97 @@
         {
         case 4:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
             for (int i = 1; i <= 10; ++i)
             {
-                int u = subWord(shift(t3, 8)) ^ rcon[i - 1];
-                t0 ^= u;  W[i][0] = t0;
-                t1 ^= t0; W[i][1] = t1;
-                t2 ^= t1; W[i][2] = t2;
-                t3 ^= t2; W[i][3] = t3;
+                int colx = subWord(shift(col3, 8)) ^ rcon[i - 1];
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
             }
 
             break;
         }
         case 6:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int rcon = 1;
-            int u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-            t0 ^= u;  W[1][2] = t0;
-            t1 ^= t0; W[1][3] = t1;
-            t2 ^= t1; W[2][0] = t2;
-            t3 ^= t2; W[2][1] = t3;
-            t4 ^= t3; W[2][2] = t4;
-            t5 ^= t4; W[2][3] = t5;
+            int col4 = Pack.littleEndianToInt(key, 16);
+            int col5 = Pack.littleEndianToInt(key, 20);
 
-            for (int i = 3; i < 12; i += 3)
+            int i = 1, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                t4 ^= t3; W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i + 1][2] = t0;
-                t1 ^= t0; W[i + 1][3] = t1;
-                t2 ^= t1; W[i + 2][0] = t2;
-                t3 ^= t2; W[i + 2][1] = t3;
-                t4 ^= t3; W[i + 2][2] = t4;
-                t5 ^= t4; W[i + 2][3] = t5;
-            }
+                                    W[i    ][0] = col4;
+                                    W[i    ][1] = col5;
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i    ][2] = col0;
+                col1 ^= col0;       W[i    ][3] = col1;
 
-            u = subWord(shift(t5, 8)) ^ rcon;
-            t0 ^= u;  W[12][0] = t0;
-            t1 ^= t0; W[12][1] = t1;
-            t2 ^= t1; W[12][2] = t2;
-            t3 ^= t2; W[12][3] = t3;
+                col2 ^= col1;       W[i + 1][0] = col2;
+                col3 ^= col2;       W[i + 1][1] = col3;
+                col4 ^= col3;       W[i + 1][2] = col4;
+                col5 ^= col4;       W[i + 1][3] = col5;
+
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i + 2][0] = col0;
+                col1 ^= col0;       W[i + 2][1] = col1;
+                col2 ^= col1;       W[i + 2][2] = col2;
+                col3 ^= col2;       W[i + 2][3] = col3;
+
+                if ((i += 3) >= 13)
+                {
+                    break;
+                }
+
+                col4 ^= col3;
+                col5 ^= col4;
+            }
 
             break;
         }
         case 8:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
-            int t6 = Pack.littleEndianToInt(key, 24); W[1][2] = t6;
-            int t7 = Pack.littleEndianToInt(key, 28); W[1][3] = t7;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int u, rcon = 1;
+            int col4 = Pack.littleEndianToInt(key, 16);     W[1][0] = col4;
+            int col5 = Pack.littleEndianToInt(key, 20);     W[1][1] = col5;
+            int col6 = Pack.littleEndianToInt(key, 24);     W[1][2] = col6;
+            int col7 = Pack.littleEndianToInt(key, 28);     W[1][3] = col7;
 
-            for (int i = 2; i < 14; i += 2)
+            int i = 2, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t7, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                u = subWord(t3);
-                t4 ^= u;  W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                t6 ^= t5; W[i + 1][2] = t6;
-                t7 ^= t6; W[i + 1][3] = t7;
-            }
+                colx = subWord(shift(col7, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
+                ++i;
 
-            u = subWord(shift(t7, 8)) ^ rcon;
-            t0 ^= u;  W[14][0] = t0;
-            t1 ^= t0; W[14][1] = t1;
-            t2 ^= t1; W[14][2] = t2;
-            t3 ^= t2; W[14][3] = t3;
+                if (i >= 15)
+                {
+                    break;
+                }
+
+                colx = subWord(col3);
+                col4 ^= colx;       W[i][0] = col4;
+                col5 ^= col4;       W[i][1] = col5;
+                col6 ^= col5;       W[i][2] = col6;
+                col7 ^= col6;       W[i][3] = col7;
+                ++i;
+            }
 
             break;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java
index abb8ba8..b73e0a5 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESFastEngine.java
@@ -10,7 +10,7 @@
 /**
  * an implementation of the AES (Rijndael), from FIPS-197.
  * <p>
- * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>.
+ * For further details see: <a href="https://csrc.nist.gov/encryption/aes/">https://csrc.nist.gov/encryption/aes/</a>.
  *
  * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
  * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
@@ -625,98 +625,97 @@
         {
         case 4:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
             for (int i = 1; i <= 10; ++i)
             {
-                int u = subWord(shift(t3, 8)) ^ rcon[i - 1];
-                t0 ^= u;  W[i][0] = t0;
-                t1 ^= t0; W[i][1] = t1;
-                t2 ^= t1; W[i][2] = t2;
-                t3 ^= t2; W[i][3] = t3;
+                int colx = subWord(shift(col3, 8)) ^ rcon[i - 1];
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
             }
 
             break;
         }
         case 6:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int rcon = 1;
-            int u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-            t0 ^= u;  W[1][2] = t0;
-            t1 ^= t0; W[1][3] = t1;
-            t2 ^= t1; W[2][0] = t2;
-            t3 ^= t2; W[2][1] = t3;
-            t4 ^= t3; W[2][2] = t4;
-            t5 ^= t4; W[2][3] = t5;
+            int col4 = Pack.littleEndianToInt(key, 16);
+            int col5 = Pack.littleEndianToInt(key, 20);
 
-            for (int i = 3; i < 12; i += 3)
+            int i = 1, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                t4 ^= t3; W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i + 1][2] = t0;
-                t1 ^= t0; W[i + 1][3] = t1;
-                t2 ^= t1; W[i + 2][0] = t2;
-                t3 ^= t2; W[i + 2][1] = t3;
-                t4 ^= t3; W[i + 2][2] = t4;
-                t5 ^= t4; W[i + 2][3] = t5;
-            }
+                                    W[i    ][0] = col4;
+                                    W[i    ][1] = col5;
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i    ][2] = col0;
+                col1 ^= col0;       W[i    ][3] = col1;
 
-            u = subWord(shift(t5, 8)) ^ rcon;
-            t0 ^= u;  W[12][0] = t0;
-            t1 ^= t0; W[12][1] = t1;
-            t2 ^= t1; W[12][2] = t2;
-            t3 ^= t2; W[12][3] = t3;
+                col2 ^= col1;       W[i + 1][0] = col2;
+                col3 ^= col2;       W[i + 1][1] = col3;
+                col4 ^= col3;       W[i + 1][2] = col4;
+                col5 ^= col4;       W[i + 1][3] = col5;
+
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i + 2][0] = col0;
+                col1 ^= col0;       W[i + 2][1] = col1;
+                col2 ^= col1;       W[i + 2][2] = col2;
+                col3 ^= col2;       W[i + 2][3] = col3;
+
+                if ((i += 3) >= 13)
+                {
+                    break;
+                }
+
+                col4 ^= col3;
+                col5 ^= col4;
+            }
 
             break;
         }
         case 8:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
-            int t6 = Pack.littleEndianToInt(key, 24); W[1][2] = t6;
-            int t7 = Pack.littleEndianToInt(key, 28); W[1][3] = t7;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int u, rcon = 1;
+            int col4 = Pack.littleEndianToInt(key, 16);     W[1][0] = col4;
+            int col5 = Pack.littleEndianToInt(key, 20);     W[1][1] = col5;
+            int col6 = Pack.littleEndianToInt(key, 24);     W[1][2] = col6;
+            int col7 = Pack.littleEndianToInt(key, 28);     W[1][3] = col7;
 
-            for (int i = 2; i < 14; i += 2)
+            int i = 2, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t7, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                u = subWord(t3);
-                t4 ^= u;  W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                t6 ^= t5; W[i + 1][2] = t6;
-                t7 ^= t6; W[i + 1][3] = t7;
-            }
+                colx = subWord(shift(col7, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
+                ++i;
 
-            u = subWord(shift(t7, 8)) ^ rcon;
-            t0 ^= u;  W[14][0] = t0;
-            t1 ^= t0; W[14][1] = t1;
-            t2 ^= t1; W[14][2] = t2;
-            t3 ^= t2; W[14][3] = t3;
+                if (i >= 15)
+                {
+                    break;
+                }
+
+                colx = subWord(col3);
+                col4 ^= colx;       W[i][0] = col4;
+                col5 ^= col4;       W[i][1] = col5;
+                col6 ^= col5;       W[i][2] = col6;
+                col7 ^= col6;       W[i][3] = col7;
+                ++i;
+            }
 
             break;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java
index 8759578..7affef9 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESLightEngine.java
@@ -10,7 +10,7 @@
 /**
  * an implementation of the AES (Rijndael), from FIPS-197.
  * <p>
- * For further details see: <a href="http://csrc.nist.gov/encryption/aes/">http://csrc.nist.gov/encryption/aes/</a>.
+ * For further details see: <a href="https://csrc.nist.gov/encryption/aes/">https://csrc.nist.gov/encryption/aes/</a>.
  *
  * This implementation is based on optimizations from Dr. Brian Gladman's paper and C code at
  * <a href="http://fp.gladman.plus.com/cryptography_technology/rijndael/">http://fp.gladman.plus.com/cryptography_technology/rijndael/</a>
@@ -187,7 +187,7 @@
             throw new IllegalArgumentException("Key length not 128/192/256 bits.");
         }
 
-        int KC = keyLen >> 2;
+        int KC = keyLen >>> 2;
         ROUNDS = KC + 6;  // This is not always true for the generalized Rijndael that allows larger block sizes
         int[][] W = new int[ROUNDS+1][4];   // 4 words in a block
 
@@ -195,98 +195,97 @@
         {
         case 4:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
             for (int i = 1; i <= 10; ++i)
             {
-                int u = subWord(shift(t3, 8)) ^ rcon[i - 1];
-                t0 ^= u;  W[i][0] = t0;
-                t1 ^= t0; W[i][1] = t1;
-                t2 ^= t1; W[i][2] = t2;
-                t3 ^= t2; W[i][3] = t3;
+                int colx = subWord(shift(col3, 8)) ^ rcon[i - 1];
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
             }
 
             break;
         }
         case 6:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int rcon = 1;
-            int u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-            t0 ^= u;  W[1][2] = t0;
-            t1 ^= t0; W[1][3] = t1;
-            t2 ^= t1; W[2][0] = t2;
-            t3 ^= t2; W[2][1] = t3;
-            t4 ^= t3; W[2][2] = t4;
-            t5 ^= t4; W[2][3] = t5;
+            int col4 = Pack.littleEndianToInt(key, 16);
+            int col5 = Pack.littleEndianToInt(key, 20);
 
-            for (int i = 3; i < 12; i += 3)
+            int i = 1, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                t4 ^= t3; W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                u = subWord(shift(t5, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i + 1][2] = t0;
-                t1 ^= t0; W[i + 1][3] = t1;
-                t2 ^= t1; W[i + 2][0] = t2;
-                t3 ^= t2; W[i + 2][1] = t3;
-                t4 ^= t3; W[i + 2][2] = t4;
-                t5 ^= t4; W[i + 2][3] = t5;
-            }
+                                    W[i    ][0] = col4;
+                                    W[i    ][1] = col5;
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i    ][2] = col0;
+                col1 ^= col0;       W[i    ][3] = col1;
 
-            u = subWord(shift(t5, 8)) ^ rcon;
-            t0 ^= u;  W[12][0] = t0;
-            t1 ^= t0; W[12][1] = t1;
-            t2 ^= t1; W[12][2] = t2;
-            t3 ^= t2; W[12][3] = t3;
+                col2 ^= col1;       W[i + 1][0] = col2;
+                col3 ^= col2;       W[i + 1][1] = col3;
+                col4 ^= col3;       W[i + 1][2] = col4;
+                col5 ^= col4;       W[i + 1][3] = col5;
+
+                colx = subWord(shift(col5, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i + 2][0] = col0;
+                col1 ^= col0;       W[i + 2][1] = col1;
+                col2 ^= col1;       W[i + 2][2] = col2;
+                col3 ^= col2;       W[i + 2][3] = col3;
+
+                if ((i += 3) >= 13)
+                {
+                    break;
+                }
+
+                col4 ^= col3;
+                col5 ^= col4;
+            }
 
             break;
         }
         case 8:
         {
-            int t0 = Pack.littleEndianToInt(key,  0); W[0][0] = t0;
-            int t1 = Pack.littleEndianToInt(key,  4); W[0][1] = t1;
-            int t2 = Pack.littleEndianToInt(key,  8); W[0][2] = t2;
-            int t3 = Pack.littleEndianToInt(key, 12); W[0][3] = t3;
-            int t4 = Pack.littleEndianToInt(key, 16); W[1][0] = t4;
-            int t5 = Pack.littleEndianToInt(key, 20); W[1][1] = t5;
-            int t6 = Pack.littleEndianToInt(key, 24); W[1][2] = t6;
-            int t7 = Pack.littleEndianToInt(key, 28); W[1][3] = t7;
+            int col0 = Pack.littleEndianToInt(key,  0);     W[0][0] = col0;
+            int col1 = Pack.littleEndianToInt(key,  4);     W[0][1] = col1;
+            int col2 = Pack.littleEndianToInt(key,  8);     W[0][2] = col2;
+            int col3 = Pack.littleEndianToInt(key, 12);     W[0][3] = col3;
 
-            int u, rcon = 1;
+            int col4 = Pack.littleEndianToInt(key, 16);     W[1][0] = col4;
+            int col5 = Pack.littleEndianToInt(key, 20);     W[1][1] = col5;
+            int col6 = Pack.littleEndianToInt(key, 24);     W[1][2] = col6;
+            int col7 = Pack.littleEndianToInt(key, 28);     W[1][3] = col7;
 
-            for (int i = 2; i < 14; i += 2)
+            int i = 2, rcon = 1, colx;
+            for (;;)
             {
-                u = subWord(shift(t7, 8)) ^ rcon; rcon <<= 1;
-                t0 ^= u;  W[i    ][0] = t0;
-                t1 ^= t0; W[i    ][1] = t1;
-                t2 ^= t1; W[i    ][2] = t2;
-                t3 ^= t2; W[i    ][3] = t3;
-                u = subWord(t3);
-                t4 ^= u;  W[i + 1][0] = t4;
-                t5 ^= t4; W[i + 1][1] = t5;
-                t6 ^= t5; W[i + 1][2] = t6;
-                t7 ^= t6; W[i + 1][3] = t7;
-            }
+                colx = subWord(shift(col7, 8)) ^ rcon; rcon <<= 1;
+                col0 ^= colx;       W[i][0] = col0;
+                col1 ^= col0;       W[i][1] = col1;
+                col2 ^= col1;       W[i][2] = col2;
+                col3 ^= col2;       W[i][3] = col3;
+                ++i;
 
-            u = subWord(shift(t7, 8)) ^ rcon;
-            t0 ^= u;  W[14][0] = t0;
-            t1 ^= t0; W[14][1] = t1;
-            t2 ^= t1; W[14][2] = t2;
-            t3 ^= t2; W[14][3] = t3;
+                if (i >= 15)
+                {
+                    break;
+                }
+
+                colx = subWord(col3);
+                col4 ^= colx;       W[i][0] = col4;
+                col5 ^= col4;       W[i][1] = col5;
+                col6 ^= col5;       W[i][2] = col6;
+                col7 ^= col6;       W[i][3] = col7;
+                ++i;
+            }
 
             break;
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java
index 6d3e5ac..b80d653 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/AESWrapEngine.java
@@ -4,7 +4,7 @@
  * an implementation of the AES Key Wrapper from the NIST Key Wrap
  * Specification.
  * <p>
- * For further details see: <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
+ * For further details see: <a href="https://csrc.nist.gov/encryption/kms/key-wrap.pdf">https://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
  */
 public class AESWrapEngine
     extends RFC3394WrapEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ARIAWrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ARIAWrapEngine.java
index 8630c89..f274d4b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ARIAWrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ARIAWrapEngine.java
@@ -4,7 +4,7 @@
  * an implementation of the ARIA Key Wrapper from the NIST Key Wrap
  * Specification.
  * <p>
- * For further details see: <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
+ * For further details see: <a href="https://csrc.nist.gov/encryption/kms/key-wrap.pdf">https://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
  */
 public class ARIAWrapEngine
     extends RFC3394WrapEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java
index cfe7f1f..33918c2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/BlowfishEngine.java
@@ -420,8 +420,9 @@
 
             xr ^= P[ROUNDS + 1];
 
+            // suppress LGTM warnings index-out-of-bounds since the loop increments s by 2
             table[s] = xr;
-            table[s + 1] = xl;
+            table[s + 1] = xl;  // lgtm [java/index-out-of-bounds]
 
             xr = xl;            // end of cycle swap
             xl = table[s];
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java
index 5ca239a..852271f 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/CamelliaWrapEngine.java
@@ -3,7 +3,7 @@
 /**
  * An implementation of the Camellia key wrapper based on RFC 3657/RFC 3394.
  * <p>
- * For further details see: <a href="http://www.ietf.org/rfc/rfc3657.txt">http://www.ietf.org/rfc/rfc3657.txt</a>.
+ * For further details see: <a href="https://www.ietf.org/rfc/rfc3657.txt">https://www.ietf.org/rfc/rfc3657.txt</a>.
  */
 public class CamelliaWrapEngine
     extends RFC3394WrapEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java
index 58a2171..75f9cb3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ChaChaEngine.java
@@ -1,5 +1,6 @@
 package org.bouncycastle.crypto.engines;
 
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.Pack;
 
 /**
@@ -180,38 +181,38 @@
 
         for (int i = rounds; i > 0; i -= 2)
         {
-            x00 += x04; x12 = rotl(x12 ^ x00, 16);
-            x08 += x12; x04 = rotl(x04 ^ x08, 12);
-            x00 += x04; x12 = rotl(x12 ^ x00, 8);
-            x08 += x12; x04 = rotl(x04 ^ x08, 7);
-            x01 += x05; x13 = rotl(x13 ^ x01, 16);
-            x09 += x13; x05 = rotl(x05 ^ x09, 12);
-            x01 += x05; x13 = rotl(x13 ^ x01, 8);
-            x09 += x13; x05 = rotl(x05 ^ x09, 7);
-            x02 += x06; x14 = rotl(x14 ^ x02, 16);
-            x10 += x14; x06 = rotl(x06 ^ x10, 12);
-            x02 += x06; x14 = rotl(x14 ^ x02, 8);
-            x10 += x14; x06 = rotl(x06 ^ x10, 7);
-            x03 += x07; x15 = rotl(x15 ^ x03, 16);
-            x11 += x15; x07 = rotl(x07 ^ x11, 12);
-            x03 += x07; x15 = rotl(x15 ^ x03, 8);
-            x11 += x15; x07 = rotl(x07 ^ x11, 7);
-            x00 += x05; x15 = rotl(x15 ^ x00, 16);
-            x10 += x15; x05 = rotl(x05 ^ x10, 12);
-            x00 += x05; x15 = rotl(x15 ^ x00, 8);
-            x10 += x15; x05 = rotl(x05 ^ x10, 7);
-            x01 += x06; x12 = rotl(x12 ^ x01, 16);
-            x11 += x12; x06 = rotl(x06 ^ x11, 12);
-            x01 += x06; x12 = rotl(x12 ^ x01, 8);
-            x11 += x12; x06 = rotl(x06 ^ x11, 7);
-            x02 += x07; x13 = rotl(x13 ^ x02, 16);
-            x08 += x13; x07 = rotl(x07 ^ x08, 12);
-            x02 += x07; x13 = rotl(x13 ^ x02, 8);
-            x08 += x13; x07 = rotl(x07 ^ x08, 7);
-            x03 += x04; x14 = rotl(x14 ^ x03, 16);
-            x09 += x14; x04 = rotl(x04 ^ x09, 12);
-            x03 += x04; x14 = rotl(x14 ^ x03, 8);
-            x09 += x14; x04 = rotl(x04 ^ x09, 7);
+            x00 += x04; x12 = Integers.rotateLeft(x12 ^ x00, 16);
+            x08 += x12; x04 = Integers.rotateLeft(x04 ^ x08, 12);
+            x00 += x04; x12 = Integers.rotateLeft(x12 ^ x00, 8);
+            x08 += x12; x04 = Integers.rotateLeft(x04 ^ x08, 7);
+            x01 += x05; x13 = Integers.rotateLeft(x13 ^ x01, 16);
+            x09 += x13; x05 = Integers.rotateLeft(x05 ^ x09, 12);
+            x01 += x05; x13 = Integers.rotateLeft(x13 ^ x01, 8);
+            x09 += x13; x05 = Integers.rotateLeft(x05 ^ x09, 7);
+            x02 += x06; x14 = Integers.rotateLeft(x14 ^ x02, 16);
+            x10 += x14; x06 = Integers.rotateLeft(x06 ^ x10, 12);
+            x02 += x06; x14 = Integers.rotateLeft(x14 ^ x02, 8);
+            x10 += x14; x06 = Integers.rotateLeft(x06 ^ x10, 7);
+            x03 += x07; x15 = Integers.rotateLeft(x15 ^ x03, 16);
+            x11 += x15; x07 = Integers.rotateLeft(x07 ^ x11, 12);
+            x03 += x07; x15 = Integers.rotateLeft(x15 ^ x03, 8);
+            x11 += x15; x07 = Integers.rotateLeft(x07 ^ x11, 7);
+            x00 += x05; x15 = Integers.rotateLeft(x15 ^ x00, 16);
+            x10 += x15; x05 = Integers.rotateLeft(x05 ^ x10, 12);
+            x00 += x05; x15 = Integers.rotateLeft(x15 ^ x00, 8);
+            x10 += x15; x05 = Integers.rotateLeft(x05 ^ x10, 7);
+            x01 += x06; x12 = Integers.rotateLeft(x12 ^ x01, 16);
+            x11 += x12; x06 = Integers.rotateLeft(x06 ^ x11, 12);
+            x01 += x06; x12 = Integers.rotateLeft(x12 ^ x01, 8);
+            x11 += x12; x06 = Integers.rotateLeft(x06 ^ x11, 7);
+            x02 += x07; x13 = Integers.rotateLeft(x13 ^ x02, 16);
+            x08 += x13; x07 = Integers.rotateLeft(x07 ^ x08, 12);
+            x02 += x07; x13 = Integers.rotateLeft(x13 ^ x02, 8);
+            x08 += x13; x07 = Integers.rotateLeft(x07 ^ x08, 7);
+            x03 += x04; x14 = Integers.rotateLeft(x14 ^ x03, 16);
+            x09 += x14; x04 = Integers.rotateLeft(x04 ^ x09, 12);
+            x03 += x04; x14 = Integers.rotateLeft(x14 ^ x03, 8);
+            x09 += x14; x04 = Integers.rotateLeft(x04 ^ x09, 7);
 
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/CramerShoupCoreEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/CramerShoupCoreEngine.java
index 9cd3f79..1461f36 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/CramerShoupCoreEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/CramerShoupCoreEngine.java
@@ -290,7 +290,7 @@
 
     protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
     {
-        return !needed ? null : (provided != null) ? provided : CryptoServicesRegistrar.getSecureRandom();
+        return needed ? CryptoServicesRegistrar.getSecureRandom(provided) : null;
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
index 9db5b42..c8a1cb3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/DESedeWrapEngine.java
@@ -305,7 +305,7 @@
      * - Compute the 20 octet SHA-1 hash on the key being wrapped.
      * - Use the first 8 octets of this hash as the checksum value.
      *
-     * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum.
+     * For details see https://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum.
      *
      * @param key the key to check,
      * @return the CMS checksum.
@@ -325,7 +325,7 @@
     }
 
     /**
-     * For details see http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
+     * For details see https://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
      *
      * @param key key to be validated.
      * @param checksum the checksum.
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java
index 7d18d62..64c7836 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC128Engine.java
@@ -12,12 +12,12 @@
  * generates keystream from a 128-bit secret key and a 128-bit initialization
  * vector.
  * <p>
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
  * </p><p>
  * It is a third phase candidate in the eStream contest, and is patent-free.
  * No attacks are known as of today (April 2007). See
  *
- * http://www.ecrypt.eu.org/stream/hcp3.html
+ * https://www.ecrypt.eu.org/stream/hcp3.html
  * </p>
  */
 public class HC128Engine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java
index a74164a..b7fd5ba 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/HC256Engine.java
@@ -12,13 +12,13 @@
  * generates keystream from a 256-bit secret key and a 256-bit initialization 
  * vector.
  * <p>
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
  * </p><p>
  * Its brother, HC-128, is a third phase candidate in the eStream contest.
  * The algorithm is patent-free. No attacks are known as of today (April 2007). 
  * See
  * 
- * http://www.ecrypt.eu.org/stream/hcp3.html
+ * https://www.ecrypt.eu.org/stream/hcp3.html
  * </p>
  */
 public class HC256Engine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java
index d2dd265..596cda3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/ISAACEngine.java
@@ -9,7 +9,7 @@
 
 /**
  * Implementation of Bob Jenkin's ISAAC (Indirection Shift Accumulate Add and Count).
- * see: http://www.burtleburtle.net/bob/rand/isaacafa.html
+ * see: https://www.burtleburtle.net/bob/rand/isaacafa.html
 */
 public class ISAACEngine
     implements StreamCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java
index a5403fa..dfeab15 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NaccacheSternEngine.java
@@ -2,7 +2,6 @@
 
 import java.math.BigInteger;
 import java.util.Vector;
-import org.bouncycastle.util.Arrays;
 
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
@@ -11,10 +10,11 @@
 import org.bouncycastle.crypto.params.NaccacheSternKeyParameters;
 import org.bouncycastle.crypto.params.NaccacheSternPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.util.Arrays;
 
 /**
  * NaccacheStern Engine. For details on this cipher, please see
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  */
 public class NaccacheSternEngine
     implements AsymmetricBlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java
index c4494c4..6812c6c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/NoekeonEngine.java
@@ -5,259 +5,253 @@
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Pack;
 
 /**
  * A Noekeon engine, using direct-key mode.
  */
-
 public class NoekeonEngine
     implements BlockCipher
 {
-    private static final int genericSize = 16; // Block and key size, as well as the amount of rounds.
-    
-    private static final int[] nullVector = 
-                               {
-                                    0x00, 0x00, 0x00, 0x00 // Used in decryption
-                               },
-        
-                               roundConstants = 
-                               {
-                                    0x80, 0x1b, 0x36, 0x6c,
-                                    0xd8, 0xab, 0x4d, 0x9a,
-                                    0x2f, 0x5e, 0xbc, 0x63,
-                                    0xc6, 0x97, 0x35, 0x6a,
-                                    0xd4
-                               };
-    
-    private int[] state   = new int[4], // a
-                  subKeys = new int[4], // k
-                  decryptKeys = new int[4];
-    
-    private boolean _initialised,
-                    _forEncryption;
-    
+    // Block and key size, as well as the amount of rounds.
+    private static final int SIZE = 16;
+
+    // Used in decryption
+    private static final byte[] roundConstants = { (byte)0x80, 0x1b, 0x36, 0x6c, (byte)0xd8, (byte)0xab, 0x4d,
+        (byte)0x9a, 0x2f, 0x5e, (byte)0xbc, 0x63, (byte)0xc6, (byte)0x97, 0x35, 0x6a, (byte)0xd4 };
+
+    private final int[] k = new int[4];
+
+    private boolean _initialised, _forEncryption;
+
     /**
-     * Create an instance of the Noekeon encryption algorithm
-     * and set some defaults
+     * Create an instance of the Noekeon encryption algorithm and set some defaults
      */
     public NoekeonEngine()
     {
         _initialised = false;
     }
-    
+
     public String getAlgorithmName()
     {
         return "Noekeon";
     }
-    
+
     public int getBlockSize()
     {
-        return genericSize;
+        return SIZE;
     }
-    
+
     /**
      * initialise
      *
-     * @param forEncryption whether or not we are for encryption.
-     * @param params the parameters required to set up the cipher.
-     * @exception IllegalArgumentException if the params argument is
-     * inappropriate.
+     * @param forEncryption
+     *            whether or not we are for encryption.
+     * @param params
+     *            the parameters required to set up the cipher.
+     * @exception IllegalArgumentException
+     *                if the params argument is inappropriate.
      */
-    public void init(
-                     boolean             forEncryption,
-                     CipherParameters    params)
+    public void init(boolean forEncryption, CipherParameters params)
     {
         if (!(params instanceof KeyParameter))
         {
-            throw new IllegalArgumentException("invalid parameter passed to Noekeon init - " + params.getClass().getName());
+            throw new IllegalArgumentException(
+                "invalid parameter passed to Noekeon init - " + params.getClass().getName());
         }
-        
-        _forEncryption = forEncryption;
-        _initialised = true;
-        
-        KeyParameter       p = (KeyParameter)params;
-        
-        setKey(p.getKey());
+
+        this._forEncryption = forEncryption;
+        this._initialised = true;
+
+        KeyParameter p = (KeyParameter)params;
+
+        Pack.bigEndianToInt(p.getKey(), 0, k, 0, 4);
+
+        if (!forEncryption)
+        {
+            // theta(k, new int[]{ 0x00, 0x00, 0x00, 0x00 });
+            {
+                int a0 = k[0], a1 = k[1], a2 = k[2], a3 = k[3];
+
+                int t = a0 ^ a2;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a1 ^= t;
+                a3 ^= t;
+
+                t = a1 ^ a3;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a0 ^= t;
+                a2 ^= t;
+
+                k[0] = a0; k[1] = a1; k[2] = a2; k[3] = a3;
+            }
+        }
     }
-    
-    public int processBlock(
-                            byte[]  in,
-                            int     inOff,
-                            byte[]  out,
-                            int     outOff)
+
+    public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
     {
         if (!_initialised)
         {
-            throw new IllegalStateException(getAlgorithmName()+" not initialised");
+            throw new IllegalStateException(getAlgorithmName() + " not initialised");
         }
-        
-        if ((inOff + genericSize) > in.length)
+        if (inOff > in.length - SIZE)
         {
             throw new DataLengthException("input buffer too short");
         }
-        
-        if ((outOff + genericSize) > out.length)
+        if (outOff > out.length - SIZE)
         {
             throw new OutputLengthException("output buffer too short");
         }
-        
-        return (_forEncryption) ? encryptBlock(in, inOff, out, outOff)
-                                : decryptBlock(in, inOff, out, outOff);
+
+        return _forEncryption ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
     }
-    
+
     public void reset()
     {
     }
-    
-    /**
-     * Re-key the cipher.
-     * <p>
-     * @param  key  the key to be used
-     */
-    private void setKey(
-                        byte[]      key)
+
+    private int encryptBlock(byte[] in, int inOff, byte[] out, int outOff)
     {
-        subKeys[0] = bytesToIntBig(key, 0);
-        subKeys[1] = bytesToIntBig(key, 4);
-        subKeys[2] = bytesToIntBig(key, 8);
-        subKeys[3] = bytesToIntBig(key, 12);
-    }
-    
-    private int encryptBlock(
-                             byte[]  in,
-                             int     inOff,
-                             byte[]  out,
-                             int     outOff)
-    {
-        state[0] = bytesToIntBig(in, inOff);
-        state[1] = bytesToIntBig(in, inOff+4);
-        state[2] = bytesToIntBig(in, inOff+8);
-        state[3] = bytesToIntBig(in, inOff+12);
-        
-        int i;
-        for (i = 0; i < genericSize; i++)
+        int a0 = Pack.bigEndianToInt(in, inOff);
+        int a1 = Pack.bigEndianToInt(in, inOff + 4);
+        int a2 = Pack.bigEndianToInt(in, inOff + 8);
+        int a3 = Pack.bigEndianToInt(in, inOff + 12);
+
+        int k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
+
+        int round = 0, t;
+        for (;;)
         {
-            state[0] ^= roundConstants[i];
-            theta(state, subKeys);
-            pi1(state);
-            gamma(state);
-            pi2(state);            
+            a0 ^= roundConstants[round] & 0xFF;
+
+            // theta(a, k);
+            {
+                t = a0 ^ a2;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a1 ^= t;
+                a3 ^= t;
+
+                a0 ^= k0;
+                a1 ^= k1;
+                a2 ^= k2;
+                a3 ^= k3;
+
+                t = a1 ^ a3;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a0 ^= t;
+                a2 ^= t;
+            }
+
+            if (++round > SIZE)
+            {
+                break;
+            }
+
+            // pi1(a);
+            {
+                a1 = Integers.rotateLeft(a1, 1);
+                a2 = Integers.rotateLeft(a2, 5);
+                a3 = Integers.rotateLeft(a3, 2);
+            }
+
+            // gamma(a);
+            {
+                a1 ^= ~a3 & ~a2;
+                a0 ^= a2 & a1;
+
+                t = a3; a3 = a0; a0 = t;
+                a2 ^= a0 ^ a1 ^ a3;
+
+                a1 ^= ~a3 & ~a2;
+                a0 ^= a2 & a1;
+            }
+
+            // pi2(a);
+            {
+                a1 = Integers.rotateLeft(a1, 31);
+                a2 = Integers.rotateLeft(a2, 27);
+                a3 = Integers.rotateLeft(a3, 30);
+            }
         }
-        
-        state[0] ^= roundConstants[i];
-        theta(state, subKeys);
-        
-        intToBytesBig(state[0], out, outOff);
-        intToBytesBig(state[1], out, outOff+4);
-        intToBytesBig(state[2], out, outOff+8);
-        intToBytesBig(state[3], out, outOff+12);
-        
-        return genericSize;
+
+        Pack.intToBigEndian(a0, out, outOff);
+        Pack.intToBigEndian(a1, out, outOff + 4);
+        Pack.intToBigEndian(a2, out, outOff + 8);
+        Pack.intToBigEndian(a3, out, outOff + 12);
+
+        return SIZE;
     }
-    
-    private int decryptBlock(
-                             byte[]  in,
-                             int     inOff,
-                             byte[]  out,
-                             int     outOff)
+
+    private int decryptBlock(byte[] in, int inOff, byte[] out, int outOff)
     {
-        state[0] = bytesToIntBig(in, inOff);
-        state[1] = bytesToIntBig(in, inOff+4);
-        state[2] = bytesToIntBig(in, inOff+8);
-        state[3] = bytesToIntBig(in, inOff+12);
-        
-        System.arraycopy(subKeys, 0, decryptKeys, 0, subKeys.length);
-        theta(decryptKeys, nullVector);
-        
-        int i;
-        for (i = genericSize; i > 0; i--)
+        int a0 = Pack.bigEndianToInt(in, inOff);
+        int a1 = Pack.bigEndianToInt(in, inOff + 4);
+        int a2 = Pack.bigEndianToInt(in, inOff + 8);
+        int a3 = Pack.bigEndianToInt(in, inOff + 12);
+
+        int k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
+
+        int round = SIZE, t;
+        for (;;)
         {
-            theta(state, decryptKeys);
-            state[0] ^= roundConstants[i];
-            pi1(state);
-            gamma(state);
-            pi2(state);
+            // theta(a, k);
+            {
+                t = a0 ^ a2;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a1 ^= t;
+                a3 ^= t;
+
+                a0 ^= k0;
+                a1 ^= k1;
+                a2 ^= k2;
+                a3 ^= k3;
+
+                t = a1 ^ a3;
+                t ^= Integers.rotateLeft(t, 8) ^ Integers.rotateLeft(t, 24);
+                a0 ^= t;
+                a2 ^= t;
+            }
+
+            a0 ^= roundConstants[round] & 0xFF;
+
+            if (--round < 0)
+            {
+                break;
+            }
+
+            // pi1(a);
+            {
+                a1 = Integers.rotateLeft(a1, 1);
+                a2 = Integers.rotateLeft(a2, 5);
+                a3 = Integers.rotateLeft(a3, 2);
+            }
+
+            // gamma(a);
+            {
+                a1 ^= ~a3 & ~a2;
+                a0 ^= a2 & a1;
+
+                t = a3; a3 = a0; a0 = t;
+                a2 ^= a0 ^ a1 ^ a3;
+
+                a1 ^= ~a3 & ~a2;
+                a0 ^= a2 & a1;
+            }
+
+            // pi2(a);
+            {
+                a1 = Integers.rotateLeft(a1, 31);
+                a2 = Integers.rotateLeft(a2, 27);
+                a3 = Integers.rotateLeft(a3, 30);
+            }
         }
-        
-        theta(state, decryptKeys);
-        state[0] ^= roundConstants[i];
-        
-        intToBytesBig(state[0], out, outOff);
-        intToBytesBig(state[1], out, outOff+4);
-        intToBytesBig(state[2], out, outOff+8);
-        intToBytesBig(state[3], out, outOff+12);
-        
-        return genericSize;
-    }
-        
-    private void gamma(int[] a)
-    {
-        a[1] ^= ~a[3] & ~a[2];
-        a[0] ^= a[2] & a[1];
-        
-        int tmp = a[3];
-        a[3]  = a[0];
-        a[0]  = tmp;
-        a[2] ^= a[0]^a[1]^a[3];
-        
-        a[1] ^= ~a[3] & ~a[2];
-        a[0] ^= a[2] & a[1];
-    }
-    
-    private void theta(int[] a, int[] k)
-    {
-        int tmp;
-        
-        tmp   = a[0]^a[2]; 
-        tmp  ^= rotl(tmp,8)^rotl(tmp,24); 
-        a[1] ^= tmp; 
-        a[3] ^= tmp; 
-        
-        for (int i = 0; i < 4; i++)
-        {
-            a[i] ^= k[i];
-        }
-        
-        tmp   = a[1]^a[3]; 
-        tmp  ^= rotl(tmp,8)^rotl(tmp,24); 
-        a[0] ^= tmp; 
-        a[2] ^= tmp;
-    }
-    
-    private void pi1(int[] a)
-    {
-        a[1] = rotl(a[1], 1);
-        a[2] = rotl(a[2], 5);
-        a[3] = rotl(a[3], 2);
-    }
-    
-    private void pi2(int[] a)
-    {
-        a[1] = rotl(a[1], 31);
-        a[2] = rotl(a[2], 27);
-        a[3] = rotl(a[3], 30);
-    }
-    
-    // Helpers
-    
-    private int bytesToIntBig(byte[] in, int off)
-    {
-        return ((in[off++]) << 24) |
-        ((in[off++] & 0xff) << 16) |
-        ((in[off++] & 0xff) <<  8) |
-         (in[off  ] & 0xff);
-    }
-    
-    private void intToBytesBig(int x, byte[] out, int off)
-    {
-        out[off++] = (byte)(x >>> 24);
-        out[off++] = (byte)(x >>> 16);
-        out[off++] = (byte)(x >>>  8);
-        out[off  ] = (byte)x;
-    }
-    
-    private int rotl(int x, int y)
-    {
-        return (x << y) | (x >>> (32-y));
+
+        Pack.intToBigEndian(a0, out, outOff);
+        Pack.intToBigEndian(a1, out, outOff + 4);
+        Pack.intToBigEndian(a2, out, outOff + 8);
+        Pack.intToBigEndian(a3, out, outOff + 12);
+
+        return SIZE;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java
index 52bc031..98208b4 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC2WrapEngine.java
@@ -351,7 +351,7 @@
      * - Compute the 20 octet SHA-1 hash on the key being wrapped.
      * - Use the first 8 octets of this hash as the checksum value.
      *
-     * For details see  http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
+     * For details see  https://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
      */
     private byte[] calculateCMSKeyChecksum(
         byte[] key)
@@ -367,7 +367,7 @@
     }
 
     /*
-     * For details see  http://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
+     * For details see  https://www.w3.org/TR/xmlenc-core/#sec-CMSKeyChecksum
      */
     private boolean checkCMSKeyChecksum(
         byte[] key,
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java
index 9fb6f55..d361d1d 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC532Engine.java
@@ -8,7 +8,7 @@
 /**
  * The specification for RC5 came from the <code>RC5 Encryption Algorithm</code>
  * publication in RSA CryptoBytes, Spring of 1995. 
- * <em>http://www.rsasecurity.com/rsalabs/cryptobytes</em>.
+ * <em>https://www.rsasecurity.com/rsalabs/cryptobytes</em>.
  * <p>
  * This implementation has a word size of 32 bits.
  * <p>
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java
index 2121a4b..dd4cc65 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RC564Engine.java
@@ -7,7 +7,7 @@
 /**
  * The specification for RC5 came from the <code>RC5 Encryption Algorithm</code>
  * publication in RSA CryptoBytes, Spring of 1995. 
- * <em>http://www.rsasecurity.com/rsalabs/cryptobytes</em>.
+ * <em>https://www.rsasecurity.com/rsalabs/cryptobytes</em>.
  * <p>
  * This implementation is set to work with a 64 bit word size.
  * <p>
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
index d2886e7..46e5cbe 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC3394WrapEngine.java
@@ -14,8 +14,8 @@
  * an implementation of the AES Key Wrapper from the NIST Key Wrap
  * Specification as described in RFC 3394.
  * <p>
- * For further details see: <a href="http://www.ietf.org/rfc/rfc3394.txt">http://www.ietf.org/rfc/rfc3394.txt</a>
- * and  <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
+ * For further details see: <a href="https://www.ietf.org/rfc/rfc3394.txt">https://www.ietf.org/rfc/rfc3394.txt</a>
+ * and  <a href="https://csrc.nist.gov/encryption/kms/key-wrap.pdf">https://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
  */
 public class RFC3394WrapEngine
     implements Wrapper
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java
index 94d15b7..3537b08 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RFC5649WrapEngine.java
@@ -154,7 +154,7 @@
             throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes");
         }
 
-        if (n == 1)
+        if (n <= 1)
         {
             throw new InvalidCipherTextException("unwrap data must be at least 16 bytes");
         }
@@ -215,12 +215,13 @@
             isValid = false;
         }
 
-        // Check the number of padded zeros
+        // Check the number of padding zeros
         int expectedZeros = upperBound - mli;
-        if (expectedZeros >= paddedPlaintext.length)
+        if (expectedZeros >= 8 || expectedZeros < 0)
         {
+            // We have to pick a "typical" amount of padding to avoid timing attacks.
             isValid = false;
-            expectedZeros = paddedPlaintext.length;
+            expectedZeros = 4;
         }
 
         byte[] zeros = new byte[expectedZeros];
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java
index f34402e..cc42899 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindedEngine.java
@@ -125,7 +125,7 @@
                 BigInteger blindedInput = r.modPow(e, m).multiply(input).mod(m);
                 BigInteger blindedResult = core.processBlock(blindedInput);
 
-                BigInteger rInv = r.modInverse(m);
+                BigInteger rInv = BigIntegers.modOddInverse(m, r);
                 result = blindedResult.multiply(rInv).mod(m);
                 // defence against Arjen Lenstra’s CRT attack
                 if (!input.equals(result.modPow(e, m)))
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java
index a8ecb9b..1d2444c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/RSABlindingEngine.java
@@ -1,13 +1,14 @@
 package org.bouncycastle.crypto.engines;
 
+import java.math.BigInteger;
+
 import org.bouncycastle.crypto.AsymmetricBlockCipher;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.params.RSABlindingParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
-
-import java.math.BigInteger;
+import org.bouncycastle.util.BigIntegers;
 
 /**
  * This does your basic RSA Chaum's blinding and unblinding as outlined in
@@ -128,7 +129,7 @@
     {
         BigInteger m = key.getModulus();
         BigInteger msg = blindedMsg;
-        BigInteger blindFactorInverse = blindingFactor.modInverse(m);
+        BigInteger blindFactorInverse = BigIntegers.modOddInverse(m, blindingFactor);
         msg = msg.multiply(blindFactorInverse);
         msg = msg.mod(m);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java
index 5b65b00..54a3588 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SEEDWrapEngine.java
@@ -3,7 +3,7 @@
 /**
  * An implementation of the SEED key wrapper based on RFC 4010/RFC 3394.
  * <p>
- * For further details see: <a href="http://www.ietf.org/rfc/rfc4010.txt">http://www.ietf.org/rfc/rfc4010.txt</a>.
+ * For further details see: <a href="https://www.ietf.org/rfc/rfc4010.txt">https://www.ietf.org/rfc/rfc4010.txt</a>.
  */
 public class SEEDWrapEngine
     extends RFC3394WrapEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java
index f25a25e..ea0e3e3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Salsa20Engine.java
@@ -7,6 +7,7 @@
 import org.bouncycastle.crypto.SkippingStreamCipher;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.Pack;
 import org.bouncycastle.util.Strings;
 
@@ -437,39 +438,39 @@
 
         for (int i = rounds; i > 0; i -= 2)
         {
-            x04 ^= rotl(x00 + x12, 7);
-            x08 ^= rotl(x04 + x00, 9);
-            x12 ^= rotl(x08 + x04, 13);
-            x00 ^= rotl(x12 + x08, 18);
-            x09 ^= rotl(x05 + x01, 7);
-            x13 ^= rotl(x09 + x05, 9);
-            x01 ^= rotl(x13 + x09, 13);
-            x05 ^= rotl(x01 + x13, 18);
-            x14 ^= rotl(x10 + x06, 7);
-            x02 ^= rotl(x14 + x10, 9);
-            x06 ^= rotl(x02 + x14, 13);
-            x10 ^= rotl(x06 + x02, 18);
-            x03 ^= rotl(x15 + x11, 7);
-            x07 ^= rotl(x03 + x15, 9);
-            x11 ^= rotl(x07 + x03, 13);
-            x15 ^= rotl(x11 + x07, 18);
+            x04 ^= Integers.rotateLeft(x00 + x12, 7);
+            x08 ^= Integers.rotateLeft(x04 + x00, 9);
+            x12 ^= Integers.rotateLeft(x08 + x04, 13);
+            x00 ^= Integers.rotateLeft(x12 + x08, 18);
+            x09 ^= Integers.rotateLeft(x05 + x01, 7);
+            x13 ^= Integers.rotateLeft(x09 + x05, 9);
+            x01 ^= Integers.rotateLeft(x13 + x09, 13);
+            x05 ^= Integers.rotateLeft(x01 + x13, 18);
+            x14 ^= Integers.rotateLeft(x10 + x06, 7);
+            x02 ^= Integers.rotateLeft(x14 + x10, 9);
+            x06 ^= Integers.rotateLeft(x02 + x14, 13);
+            x10 ^= Integers.rotateLeft(x06 + x02, 18);
+            x03 ^= Integers.rotateLeft(x15 + x11, 7);
+            x07 ^= Integers.rotateLeft(x03 + x15, 9);
+            x11 ^= Integers.rotateLeft(x07 + x03, 13);
+            x15 ^= Integers.rotateLeft(x11 + x07, 18);
 
-            x01 ^= rotl(x00 + x03, 7);
-            x02 ^= rotl(x01 + x00, 9);
-            x03 ^= rotl(x02 + x01, 13);
-            x00 ^= rotl(x03 + x02, 18);
-            x06 ^= rotl(x05 + x04, 7);
-            x07 ^= rotl(x06 + x05, 9);
-            x04 ^= rotl(x07 + x06, 13);
-            x05 ^= rotl(x04 + x07, 18);
-            x11 ^= rotl(x10 + x09, 7);
-            x08 ^= rotl(x11 + x10, 9);
-            x09 ^= rotl(x08 + x11, 13);
-            x10 ^= rotl(x09 + x08, 18);
-            x12 ^= rotl(x15 + x14, 7);
-            x13 ^= rotl(x12 + x15, 9);
-            x14 ^= rotl(x13 + x12, 13);
-            x15 ^= rotl(x14 + x13, 18);
+            x01 ^= Integers.rotateLeft(x00 + x03, 7);
+            x02 ^= Integers.rotateLeft(x01 + x00, 9);
+            x03 ^= Integers.rotateLeft(x02 + x01, 13);
+            x00 ^= Integers.rotateLeft(x03 + x02, 18);
+            x06 ^= Integers.rotateLeft(x05 + x04, 7);
+            x07 ^= Integers.rotateLeft(x06 + x05, 9);
+            x04 ^= Integers.rotateLeft(x07 + x06, 13);
+            x05 ^= Integers.rotateLeft(x04 + x07, 18);
+            x11 ^= Integers.rotateLeft(x10 + x09, 7);
+            x08 ^= Integers.rotateLeft(x11 + x10, 9);
+            x09 ^= Integers.rotateLeft(x08 + x11, 13);
+            x10 ^= Integers.rotateLeft(x09 + x08, 18);
+            x12 ^= Integers.rotateLeft(x15 + x14, 7);
+            x13 ^= Integers.rotateLeft(x12 + x15, 9);
+            x14 ^= Integers.rotateLeft(x13 + x12, 13);
+            x15 ^= Integers.rotateLeft(x14 + x13, 18);
         }
 
         x[ 0] = x00 + input[ 0];
@@ -490,19 +491,6 @@
         x[15] = x15 + input[15];
     }
 
-    /**
-     * Rotate left
-     *
-     * @param   x   value to rotate
-     * @param   y   amount to rotate x
-     *
-     * @return  rotated x
-     */
-    protected static int rotl(int x, int y)
-    {
-        return (x << y) | (x >>> -y);
-    }
-
     private void resetLimitCounter()
     {
         cW0 = 0;
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java
index 4a20ea4..63bac69 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/SerpentEngine.java
@@ -10,7 +10,7 @@
  * Serpent was designed by Ross Anderson, Eli Biham and Lars Knudsen as a
  * candidate algorithm for the NIST AES Quest.
  * <p>
- * For full details see <a href="http://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a>
+ * For full details see <a href="https://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a>
  */
 public final class SerpentEngine
     extends SerpentEngineBase
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java
index a34e016..a59f455 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/TnepresEngine.java
@@ -12,7 +12,7 @@
  * with test vectors in the AES submission and the resulting confusion lead to the Tnepres cipher
  * as well, which is a byte swapped version of Serpent.
  * <p>
- * For full details see <a href="http://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a>
+ * For full details see <a href="https://www.cl.cam.ac.uk/~rja14/serpent.html">The Serpent home page</a>
  */
 public final class TnepresEngine
     extends SerpentEngineBase
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc128Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc128Engine.java
index 9ee3f08..9c2f447 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc128Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc128Engine.java
@@ -4,7 +4,7 @@
 
 /**
  * Zuc256 implementation.
- * Based on http://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
+ * Based on https://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
  */
 public final class Zuc128Engine
     extends Zuc128CoreEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256CoreEngine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256CoreEngine.java
index c0c90c6..c42a72e 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256CoreEngine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256CoreEngine.java
@@ -4,7 +4,7 @@
 
 /**
  * Zuc256 implementation.
- * Based on http://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
+ * Based on https://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
  */
 public class Zuc256CoreEngine
     extends Zuc128CoreEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256Engine.java b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256Engine.java
index 2e5853a..c80610c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256Engine.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/engines/Zuc256Engine.java
@@ -4,7 +4,7 @@
 
 /**
  * Zuc256 implementation.
- * Based on http://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
+ * Based on https://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
  */
 public final class Zuc256Engine
     extends Zuc256CoreEngine
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java
index fa45b8e..c4e9eee 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/Argon2BytesGenerator.java
@@ -4,16 +4,14 @@
 import org.bouncycastle.crypto.digests.Blake2bDigest;
 import org.bouncycastle.crypto.params.Argon2Parameters;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Longs;
 import org.bouncycastle.util.Pack;
-import org.bouncycastle.util.encoders.Hex;
-
 
 /**
  * Argon2 PBKDF - Based on the results of https://password-hashing.net/ and https://www.ietf.org/archive/id/draft-irtf-cfrg-argon2-03.txt
  */
 public class Argon2BytesGenerator
 {
-
     private static final int ARGON2_BLOCK_SIZE = 1024;
     private static final int ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8;
 
@@ -34,20 +32,17 @@
     /* Minimum and maximum number of passes */
     private static final int MIN_ITERATIONS = 1;
 
+    private static final long M32L = 0xFFFFFFFFL;
+
+    private static final byte[] ZERO_BYTES = new byte[4];
+
+    private Argon2Parameters parameters;
     private Block[] memory;
-
-
     private int segmentLength;
     private int laneLength;
 
-
-    private Argon2Parameters parameters;
-
-    private byte[] result;
-
     public Argon2BytesGenerator()
     {
-
     }
 
     /**
@@ -59,7 +54,6 @@
     {
         this.parameters = parameters;
 
-
         if (parameters.getLanes() < Argon2BytesGenerator.MIN_PARALLELISM)
         {
             throw new IllegalStateException("lanes must be greater than " + Argon2BytesGenerator.MIN_PARALLELISM);
@@ -102,11 +96,11 @@
             throw new IllegalStateException("output length less than " + Argon2BytesGenerator.MIN_OUTLEN);
         }
 
-        initialize(password, outLen);
-        fillMemoryBlocks();
-        digest(outLen);
+        byte[] tmpBlockBytes = new byte[ARGON2_BLOCK_SIZE];
 
-        System.arraycopy(result, 0, out, outOff, outLen);
+        initialize(tmpBlockBytes, password, outLen);
+        fillMemoryBlocks();
+        digest(tmpBlockBytes, out, outOff, outLen);
 
         reset();
 
@@ -117,14 +111,17 @@
     private void reset()
     {
         // Reset memory.
-        for (int i = 0; i < memory.length; i++)
+        if (null != memory)
         {
-            Block b = memory[i];
-
-            b.clear();
+            for (int i = 0; i < memory.length; i++)
+            {
+                Block b = memory[i];
+                if (null != b)
+                {
+                    b.clear();
+                }
+            }
         }
-        memory = null;
-        Arrays.fill(result, (byte)0);
     }
 
     private void doInit(Argon2Parameters parameters)
@@ -147,7 +144,6 @@
         initMemory(memoryBlocks);
     }
 
-
     private void initMemory(int memoryBlocks)
     {
         this.memory = new Block[memoryBlocks];
@@ -162,13 +158,18 @@
     {
         FillBlock filler = new FillBlock();
         Position position = new Position();
-        for (int i = 0; i < parameters.getIterations(); i++)
+        for (int pass = 0; pass < parameters.getIterations(); ++pass)
         {
-            for (int j = 0; j < ARGON2_SYNC_POINTS; j++)
+            position.pass = pass;
+
+            for (int slice = 0; slice < ARGON2_SYNC_POINTS; ++slice)
             {
-                for (int k = 0; k < parameters.getLanes(); k++)
+                position.slice = slice;
+
+                for (int lane = 0; lane < parameters.getLanes(); ++lane)
                 {
-                    position.update(i, k, j, 0);
+                    position.lane = lane;
+
                     fillSegment(filler, position);
                 }
             }
@@ -177,8 +178,7 @@
 
     private void fillSegment(FillBlock filler, Position position)
     {
-
-        Block addressBlock = null, inputBlock = null, zeroBlock = null;
+        Block addressBlock = null, inputBlock = null;
 
         boolean dataIndependentAddressing = isDataIndependentAddressing(position);
         int startingIndex = getStartingIndex(position);
@@ -188,26 +188,25 @@
         if (dataIndependentAddressing)
         {
             addressBlock = filler.addressBlock.clear();
-            zeroBlock = filler.zeroBlock.clear();
             inputBlock = filler.inputBlock.clear();
 
-            initAddressBlocks(filler, position, zeroBlock, inputBlock, addressBlock);
+            initAddressBlocks(filler, position, inputBlock, addressBlock);
         }
 
-        for (position.index = startingIndex; position.index < segmentLength; position.index++, currentOffset++, prevOffset++)
-        {
-            prevOffset = rotatePrevOffset(currentOffset, prevOffset);
+        final boolean withXor = isWithXor(position);
 
-            long pseudoRandom = getPseudoRandom(filler, position, addressBlock, inputBlock, zeroBlock, prevOffset, dataIndependentAddressing);
+        for (int index = startingIndex; index < segmentLength; ++index)
+        {
+            long pseudoRandom = getPseudoRandom(filler, index, addressBlock, inputBlock, prevOffset, dataIndependentAddressing);
             int refLane = getRefLane(position, pseudoRandom);
-            int refColumn = getRefColumn(position, pseudoRandom, refLane == position.lane);
+            int refColumn = getRefColumn(position, index, pseudoRandom, refLane == position.lane);
 
             /* 2 Creating a new block */
             Block prevBlock = memory[prevOffset];
             Block refBlock = memory[((laneLength) * refLane + refColumn)];
             Block currentBlock = memory[currentOffset];
 
-            if (isWithXor(position))
+            if (withXor)
             {
                 filler.fillBlockWithXor(prevBlock, refBlock, currentBlock);
             }
@@ -215,6 +214,9 @@
             {
                 filler.fillBlock(prevBlock, refBlock, currentBlock);
             }
+
+            prevOffset = currentOffset;
+            currentOffset++;
         }
     }
 
@@ -227,7 +229,7 @@
             );
     }
 
-    private void initAddressBlocks(FillBlock filler, Position position, Block zeroBlock, Block inputBlock, Block addressBlock)
+    private void initAddressBlocks(FillBlock filler, Position position, Block inputBlock, Block addressBlock)
     {
         inputBlock.v[0] = intToLong(position.pass);
         inputBlock.v[1] = intToLong(position.lane);
@@ -239,7 +241,7 @@
         if ((position.pass == 0) && (position.slice == 0))
         {
             /* Don't forget to generate the first block of addresses: */
-            nextAddresses(filler, zeroBlock, inputBlock, addressBlock);
+            nextAddresses(filler, inputBlock, addressBlock);
         }
     }
 
@@ -262,15 +264,6 @@
         }
     }
 
-    private int rotatePrevOffset(int currentOffset, int prevOffset)
-    {
-        if (currentOffset % laneLength == 1)
-        {
-            prevOffset = currentOffset - 1;
-        }
-        return prevOffset;
-    }
-
     private static int getStartingIndex(Position position)
     {
         if ((position.pass == 0) && (position.slice == 0))
@@ -283,24 +276,26 @@
         }
     }
 
-    private void nextAddresses(FillBlock filler, Block zeroBlock, Block inputBlock, Block addressBlock)
+    private void nextAddresses(FillBlock filler, Block inputBlock, Block addressBlock)
     {
         inputBlock.v[6]++;
-        filler.fillBlock(zeroBlock, inputBlock, addressBlock);
-        filler.fillBlock(zeroBlock, addressBlock, addressBlock);
+        filler.fillBlock(inputBlock, addressBlock);
+        filler.fillBlock(addressBlock, addressBlock);
     }
 
     /* 1.2 Computing the index of the reference block */
     /* 1.2.1 Taking pseudo-random value from the previous block */
-    private long getPseudoRandom(FillBlock filler, Position position, Block addressBlock, Block inputBlock, Block zeroBlock, int prevOffset, boolean dataIndependentAddressing)
+    private long getPseudoRandom(FillBlock filler, int index, Block addressBlock, Block inputBlock, int prevOffset,
+        boolean dataIndependentAddressing)
     {
         if (dataIndependentAddressing)
         {
-            if (position.index % ARGON2_ADDRESSES_IN_BLOCK == 0)
+            int addressIndex = index % ARGON2_ADDRESSES_IN_BLOCK;
+            if (addressIndex == 0)
             {
-                nextAddresses(filler, zeroBlock, inputBlock, addressBlock);
+                nextAddresses(filler, inputBlock, addressBlock);
             }
-            return addressBlock.v[position.index % ARGON2_ADDRESSES_IN_BLOCK];
+            return addressBlock.v[addressIndex];
         }
         else
         {
@@ -320,10 +315,8 @@
         return refLane;
     }
 
-    private int getRefColumn(Position position, long pseudoRandom,
-                             boolean sameLane)
+    private int getRefColumn(Position position, int index, long pseudoRandom, boolean sameLane)
     {
-
         int referenceAreaSize;
         int startPosition;
 
@@ -334,14 +327,13 @@
             if (sameLane)
             {
                 /* The same lane => add current segment */
-                referenceAreaSize = position.slice * segmentLength + position.index - 1;
+                referenceAreaSize = position.slice * segmentLength + index - 1;
             }
             else
             {
                 /* pass == 0 && !sameLane => position.slice > 0*/
-                referenceAreaSize = position.slice * segmentLength + ((position.index == 0) ? (-1) : 0);
+                referenceAreaSize = position.slice * segmentLength + ((index == 0) ? (-1) : 0);
             }
-
         }
         else
         {
@@ -349,23 +341,22 @@
 
             if (sameLane)
             {
-                referenceAreaSize = laneLength - segmentLength + position.index - 1;
+                referenceAreaSize = laneLength - segmentLength + index - 1;
             }
             else
             {
-                referenceAreaSize = laneLength - segmentLength + ((position.index == 0) ? (-1) : 0);
+                referenceAreaSize = laneLength - segmentLength + ((index == 0) ? (-1) : 0);
             }
         }
 
         long relativePosition = pseudoRandom & 0xFFFFFFFFL;
-//        long relativePosition = pseudoRandom << 32 >>> 32;
         relativePosition = (relativePosition * relativePosition) >>> 32;
         relativePosition = referenceAreaSize - 1 - ((referenceAreaSize * relativePosition) >>> 32);
 
         return (int)(startPosition + relativePosition) % laneLength;
     }
 
-    private void digest(int outputLength)
+    private void digest(byte[] tmpBlockBytes, byte[] out, int outOff, int outLen)
     {
         Block finalBlock = memory[laneLength - 1];
 
@@ -376,54 +367,28 @@
             finalBlock.xorWith(memory[lastBlockInLane]);
         }
 
-        byte[] finalBlockBytes = finalBlock.toBytes();
+        finalBlock.toBytes(tmpBlockBytes);
 
-        result = hash(finalBlockBytes, outputLength);
-    }
-
-    /**
-     * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X)
-     * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH)
-     */
-    private byte[] initialHash(Argon2Parameters parameters, int outputLength, byte[] password)
-    {
-        Blake2bDigest blake = new Blake2bDigest(ARGON2_PREHASH_DIGEST_LENGTH * 8);
-
-        addIntToLittleEndian(blake, parameters.getLanes());
-        addIntToLittleEndian(blake, outputLength);
-        addIntToLittleEndian(blake, parameters.getMemory());
-        addIntToLittleEndian(blake, parameters.getIterations());
-        addIntToLittleEndian(blake, parameters.getVersion());
-        addIntToLittleEndian(blake, parameters.getType());
-
-        addByteString(blake, password);
-        addByteString(blake, parameters.getSalt());
-        addByteString(blake, parameters.getSecret());
-        addByteString(blake, parameters.getAdditional());
-
-        byte[] blake2hash = new byte[blake.getDigestSize()];
-        blake.doFinal(blake2hash, 0);
-
-        return blake2hash;
+        hash(tmpBlockBytes, out, outOff, outLen);
     }
 
     /**
      * H' - hash - variable length hash function
      */
-    private byte[] hash(byte[] input, int outputLength)
+    private void hash(byte[] input, byte[] out, int outOff, int outLen)
     {
-        byte[] result = new byte[outputLength];
-        byte[] outlenBytes = Pack.intToLittleEndian(outputLength);
+        byte[] outLenBytes = new byte[4];
+        Pack.intToLittleEndian(outLen, outLenBytes, 0);
 
         int blake2bLength = 64;
 
-        if (outputLength <= blake2bLength)
+        if (outLen <= blake2bLength)
         {
-            Blake2bDigest blake = new Blake2bDigest(outputLength * 8);
+            Blake2bDigest blake = new Blake2bDigest(outLen * 8);
 
-            blake.update(outlenBytes, 0, outlenBytes.length);
+            blake.update(outLenBytes, 0, outLenBytes.length);
             blake.update(input, 0, input.length);
-            blake.doFinal(result, 0);
+            blake.doFinal(out, outOff);
         }
         else
         {
@@ -431,35 +396,33 @@
             byte[] outBuffer = new byte[blake2bLength];
 
             /* V1 */
-            digest.update(outlenBytes, 0, outlenBytes.length);
+            digest.update(outLenBytes, 0, outLenBytes.length);
             digest.update(input, 0, input.length);
             digest.doFinal(outBuffer, 0);
 
-            System.arraycopy(outBuffer, 0, result, 0, blake2bLength / 2);
+            int halfLen = blake2bLength / 2, outPos = outOff;
+            System.arraycopy(outBuffer, 0, out, outPos, halfLen);
+            outPos += halfLen;
 
-            int r = ((outputLength + 31) / 32) - 2;
+            int r = ((outLen + 31) / 32) - 2;
 
-            int position = blake2bLength / 2;
-
-            for (int i = 2; i <= r; i++, position += blake2bLength / 2)
+            for (int i = 2; i <= r; i++, outPos += halfLen)
             {
                 /* V2 to Vr */
                 digest.update(outBuffer, 0, outBuffer.length);
                 digest.doFinal(outBuffer, 0);
 
-                System.arraycopy(outBuffer, 0, result, position, blake2bLength / 2);
+                System.arraycopy(outBuffer, 0, out, outPos, halfLen);
             }
 
-            int lastLength = outputLength - 32 * r;
+            int lastLength = outLen - 32 * r;
 
             /* Vr+1 */
             digest = new Blake2bDigest(lastLength * 8);
 
             digest.update(outBuffer, 0, outBuffer.length);
-            digest.doFinal(result, position);
+            digest.doFinal(out, outPos);
         }
-
-        return result;
     }
 
     private static void roundFunction(Block block,
@@ -468,117 +431,124 @@
                                       int v8, int v9, int v10, int v11,
                                       int v12, int v13, int v14, int v15)
     {
+        final long[] v = block.v;
 
-        F(block, v0, v4, v8, v12);
-        F(block, v1, v5, v9, v13);
-        F(block, v2, v6, v10, v14);
-        F(block, v3, v7, v11, v15);
+        F(v, v0, v4, v8, v12);
+        F(v, v1, v5, v9, v13);
+        F(v, v2, v6, v10, v14);
+        F(v, v3, v7, v11, v15);
 
-        F(block, v0, v5, v10, v15);
-        F(block, v1, v6, v11, v12);
-        F(block, v2, v7, v8, v13);
-        F(block, v3, v4, v9, v14);
+        F(v, v0, v5, v10, v15);
+        F(v, v1, v6, v11, v12);
+        F(v, v2, v7, v8, v13);
+        F(v, v3, v4, v9, v14);
     }
 
-    private static void F(Block block, int a, int b, int c, int d)
+    private static void F(long[] v, int a, int b, int c, int d)
     {
-        fBlaMka(block, a, b);
-        rotr64(block, d, a, 32);
+        quarterRound(v, a, b, d, 32);
+        quarterRound(v, c, d, b, 24);
+        quarterRound(v, a, b, d, 16);
+        quarterRound(v, c, d, b, 63);
+    }
 
-        fBlaMka(block, c, d);
-        rotr64(block, b, c, 24);
+    private static void quarterRound(long[] v, int x, int y, int z, int s)
+    {
+//        fBlaMka(v, x, y);
+//        rotr64(v, z, x, s);
 
-        fBlaMka(block, a, b);
-        rotr64(block, d, a, 16);
+        long a = v[x], b = v[y], c = v[z];
 
-        fBlaMka(block, c, d);
-        rotr64(block, b, c, 63);
+        a += b + 2 * (a & M32L) * (b & M32L);
+        c = Longs.rotateRight(c ^ a, s);
+
+        v[x] = a;
+        v[z] = c;
     }
 
     /*designed by the Lyra PHC team */
     /* a <- a + b + 2*aL*bL
      * + == addition modulo 2^64
      * aL = least 32 bit */
-    private static void fBlaMka(Block block, int x, int y)
-    {
-        final long m = 0xFFFFFFFFL;
-        final long xy = (block.v[x] & m) * (block.v[y] & m);
+//    private static void fBlaMka(long[] v, int x, int y)
+//    {
+//        final long a = v[x], b = v[y];
+//        final long ab = (a & M32L) * (b & M32L);
+//
+//        v[x] = a + b + 2 * ab;
+//    }
+//
+//    private static void rotr64(long[] v, int x, int y, int s)
+//    {
+//        v[x] = Longs.rotateRight(v[x] ^ v[y], s);
+//    }
 
-        block.v[x] = block.v[x] + block.v[y] + 2 * xy;
+    private void initialize(byte[] tmpBlockBytes, byte[] password, int outputLength)
+    {
+        /**
+         * H0 = H64(p, τ, m, t, v, y, |P|, P, |S|, S, |L|, K, |X|, X)
+         * -> 64 byte (ARGON2_PREHASH_DIGEST_LENGTH)
+         */
+
+        Blake2bDigest blake = new Blake2bDigest(ARGON2_PREHASH_DIGEST_LENGTH * 8);
+
+        int[] values = { parameters.getLanes(), outputLength, parameters.getMemory(), parameters.getIterations(),
+            parameters.getVersion(), parameters.getType() };
+
+        Pack.intToLittleEndian(values, tmpBlockBytes, 0);
+        blake.update(tmpBlockBytes, 0, values.length * 4);
+
+        addByteString(tmpBlockBytes, blake, password);
+        addByteString(tmpBlockBytes, blake, parameters.getSalt());
+        addByteString(tmpBlockBytes, blake, parameters.getSecret());
+        addByteString(tmpBlockBytes, blake, parameters.getAdditional());
+
+        byte[] initialHashWithZeros = new byte[ARGON2_PREHASH_SEED_LENGTH];
+        blake.doFinal(initialHashWithZeros, 0);
+
+        fillFirstBlocks(tmpBlockBytes, initialHashWithZeros);
     }
 
-    private static void rotr64(Block block, int v, int w, long c)
+    private static void addByteString(byte[] tmpBlockBytes, Digest digest, byte[] octets)
     {
-        final long temp = block.v[v] ^ block.v[w];
-        block.v[v] = (temp >>> c) | (temp << (64 - c));
-    }
-
-    private void initialize(byte[] password, int outputLength)
-    {
-        byte[] initialHash = initialHash(parameters, outputLength, password);
-
-        fillFirstBlocks(initialHash);
-    }
-
-    private static void addIntToLittleEndian(Digest digest, int n)
-    {
-        digest.update((byte)(n));
-        digest.update((byte)(n >>> 8));
-        digest.update((byte)(n >>> 16));
-        digest.update((byte)(n >>> 24));
-    }
-
-    private static void addByteString(Digest digest, byte[] octets)
-    {
-        if (octets != null)
+        if (null == octets)
         {
-            addIntToLittleEndian(digest, octets.length);
-            digest.update(octets, 0, octets.length);
+            digest.update(ZERO_BYTES, 0, 4);
+            return;
         }
-        else
-        {
-            addIntToLittleEndian(digest, 0);
-        }
+
+        Pack.intToLittleEndian(octets.length, tmpBlockBytes, 0);
+        digest.update(tmpBlockBytes, 0, 4);
+        digest.update(octets, 0, octets.length);
     }
 
     /**
      * (H0 || 0 || i) 72 byte -> 1024 byte
      * (H0 || 1 || i) 72 byte -> 1024 byte
      */
-    private void fillFirstBlocks(byte[] initialHash)
+    private void fillFirstBlocks(byte[] tmpBlockBytes, byte[] initialHashWithZeros)
     {
-        final byte[] zeroBytes = {0, 0, 0, 0};
-        final byte[] oneBytes = {1, 0, 0, 0};
-
-        byte[] initialHashWithZeros = getInitialHashLong(initialHash, zeroBytes);
-        byte[] initialHashWithOnes = getInitialHashLong(initialHash, oneBytes);
+        byte[] initialHashWithOnes = new byte[ARGON2_PREHASH_SEED_LENGTH];
+        System.arraycopy(initialHashWithZeros, 0, initialHashWithOnes, 0, ARGON2_PREHASH_DIGEST_LENGTH);
+//        Pack.intToLittleEndian(1, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH);
+        initialHashWithOnes[ARGON2_PREHASH_DIGEST_LENGTH] = 1;
 
         for (int i = 0; i < parameters.getLanes(); i++)
         {
             Pack.intToLittleEndian(i, initialHashWithZeros, ARGON2_PREHASH_DIGEST_LENGTH + 4);
             Pack.intToLittleEndian(i, initialHashWithOnes, ARGON2_PREHASH_DIGEST_LENGTH + 4);
 
-            byte[] blockhashBytes = hash(initialHashWithZeros, ARGON2_BLOCK_SIZE);
-            memory[i * laneLength + 0].fromBytes(blockhashBytes);
+            hash(initialHashWithZeros, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+            memory[i * laneLength + 0].fromBytes(tmpBlockBytes);
 
-            blockhashBytes = hash(initialHashWithOnes, ARGON2_BLOCK_SIZE);
-            memory[i * laneLength + 1].fromBytes(blockhashBytes);
+            hash(initialHashWithOnes, tmpBlockBytes, 0, ARGON2_BLOCK_SIZE);
+            memory[i * laneLength + 1].fromBytes(tmpBlockBytes);
         }
     }
 
-    private byte[] getInitialHashLong(byte[] initialHash, byte[] appendix)
-    {
-        byte[] initialHashLong = new byte[ARGON2_PREHASH_SEED_LENGTH];
-
-        System.arraycopy(initialHash, 0, initialHashLong, 0, ARGON2_PREHASH_DIGEST_LENGTH);
-        System.arraycopy(appendix, 0, initialHashLong, ARGON2_PREHASH_DIGEST_LENGTH, 4);
-
-        return initialHashLong;
-    }
-
     private long intToLong(int x)
     {
-        return (long)(x & 0xffffffffL);
+        return (long)(x & M32L);
     }
 
     private static class FillBlock
@@ -587,7 +557,6 @@
         Block Z = new Block();
 
         Block addressBlock = new Block();
-        Block zeroBlock = new Block();
         Block inputBlock = new Block();
 
         private void applyBlake()
@@ -625,16 +594,16 @@
             }
         }
 
+        private void fillBlock(Block Y, Block currentBlock)
+        {
+            Z.copyBlock(Y);
+            applyBlake();
+            currentBlock.xor(Y, Z);
+        }
+
         private void fillBlock(Block X, Block Y, Block currentBlock)
         {
-            if (X == zeroBlock)
-            {
-                R.copyBlock(Y);
-            }
-            else
-            {
-                R.xor(X, Y);
-            }
+            R.xor(X, Y);
             Z.copyBlock(R);
             applyBlake();
             currentBlock.xor(R, Z);
@@ -645,7 +614,7 @@
             R.xor(X, Y);
             Z.copyBlock(R);
             applyBlake();
-            currentBlock.xor(R, Z, currentBlock);
+            currentBlock.xorWith(R, Z);
         }
     }
 
@@ -663,27 +632,22 @@
 
         void fromBytes(byte[] input)
         {
-            if (input.length != ARGON2_BLOCK_SIZE)
+            if (input.length < ARGON2_BLOCK_SIZE)
             {
                 throw new IllegalArgumentException("input shorter than blocksize");
             }
 
-            for (int i = 0; i < SIZE; i++)
-            {
-                v[i] = Pack.littleEndianToLong(input, i * 8);
-            }
+            Pack.littleEndianToLong(input, 0, v);
         }
 
-        byte[] toBytes()
+        void toBytes(byte[] output)
         {
-            byte[] result = new byte[ARGON2_BLOCK_SIZE];
-
-            for (int i = 0; i < SIZE; i++)
+            if (output.length < ARGON2_BLOCK_SIZE)
             {
-                Pack.longToLittleEndian(v[i], result, i * 8);
+                throw new IllegalArgumentException("output shorter than blocksize");
             }
 
-            return result;
+            Pack.longToLittleEndian(v, output, 0);
         }
 
         private void copyBlock(Block other)
@@ -693,37 +657,29 @@
 
         private void xor(Block b1, Block b2)
         {
+            long[] v0 = v, v1 = b1.v, v2 = b2.v;
             for (int i = 0; i < SIZE; i++)
             {
-                v[i] = b1.v[i] ^ b2.v[i];
+                v0[i] = v1[i] ^ v2[i];
             }
         }
 
-        public void xor(Block b1, Block b2, Block b3)
+        private void xorWith(Block b1)
         {
+            long[] v0 = v, v1 = b1.v;
             for (int i = 0; i < SIZE; i++)
             {
-                v[i] = b1.v[i] ^ b2.v[i] ^ b3.v[i];
+                v0[i] ^= v1[i];
             }
         }
 
-        private void xorWith(Block other)
+        private void xorWith(Block b1, Block b2)
         {
-            for (int i = 0; i < v.length; i++)
-            {
-                v[i] = v[i] ^ other.v[i];
-            }
-        }
-
-        public String toString()
-        {
-            StringBuffer result = new StringBuffer();
+            long[] v0 = v, v1 = b1.v, v2 = b2.v;
             for (int i = 0; i < SIZE; i++)
             {
-                result.append(Hex.toHexString(Pack.longToLittleEndian(v[i])));
+                v0[i] ^= v1[i] ^ v2[i];
             }
-
-            return result.toString();
         }
 
         public Block clear()
@@ -738,19 +694,9 @@
         int pass;
         int lane;
         int slice;
-        int index;
 
         Position()
         {
-
-        }
-
-        void update(int pass, int lane, int slice, int index)
-        {
-            this.pass = pass;
-            this.lane = lane;
-            this.slice = slice;
-            this.index = index;
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/BCrypt.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/BCrypt.java
index 80eab44..10700f2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/BCrypt.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/BCrypt.java
@@ -368,7 +368,7 @@
             xr ^= P[ROUNDS + 1];
 
             table[s] = xr;
-            table[s + 1] = xl;
+            table[s + 1] = xl;  // lgtm [java/index-out-of-bounds]
 
             xr = xl;            // end of cycle swap
             xl = table[s];
@@ -489,8 +489,9 @@
             }
             xr ^= P[ROUNDS + 1];
 
+            // suppress LGTM warnings index-out-of-bounds since the loop increments s by 4
             table[s] = xr;
-            table[s + 1] = xl;
+            table[s + 1] = xl;  // lgtm [java/index-out-of-bounds]
 
             yl = salt32Bit[2] ^ xr;
             yr = salt32Bit[3] ^ xl;
@@ -508,8 +509,8 @@
             }
             yr ^= P[ROUNDS + 1];
 
-            table[s + 2] = yr;
-            table[s + 3] = yl;
+            table[s + 2] = yr;  // lgtm [java/index-out-of-bounds]
+            table[s + 3] = yl;  // lgtm [java/index-out-of-bounds]
 
             xl = salt32Bit[0] ^ yr;
             xr = salt32Bit[1] ^ yl;
@@ -614,28 +615,29 @@
     }
 
     /**
-     * Calculates the <b>bcrypt</b> hash of a password.
+     * Calculates the <b>bcrypt</b> hash of an input - note for processing general passwords you want to
+     * make sure the password is terminated in a manner similar to what is done by passwordToByteArray().
      * <p>
      * This implements the raw <b>bcrypt</b> function as defined in the bcrypt specification, not
      * the crypt encoded version implemented in OpenBSD.
      * </p>
-     * @param password the password bytes (up to 72 bytes) to use for this invocation.
+     * @param pwInput    the password bytes (up to 72 bytes) to use for this invocation.
      * @param salt     the 128 bit salt to use for this invocation.
      * @param cost     the bcrypt cost parameter. The cost of the bcrypt function grows as
      *                 <code>2^cost</code>. Legal values are 4..31 inclusive.
      * @return the output of the raw bcrypt operation: a 192 bit (24 byte) hash.
      */
-    public static byte[] generate(byte[] password, byte[] salt, int cost)
+    public static byte[] generate(byte[] pwInput, byte[] salt, int cost)
     {
-        if (password == null || salt == null)
+        if (pwInput == null || salt == null)
         {
-            throw new IllegalArgumentException("Password and salt are required");
+            throw new IllegalArgumentException("pwInput and salt are required");
         }
         if (salt.length != SALT_SIZE_BYTES)
         {
             throw new IllegalArgumentException("BCrypt salt must be 128 bits");
         }
-        if (password.length > MAX_PASSWORD_BYTES)
+        if (pwInput.length > MAX_PASSWORD_BYTES)
         {
             throw new IllegalArgumentException("BCrypt password must be <= 72 bytes");
         }
@@ -644,6 +646,6 @@
             throw new IllegalArgumentException("BCrypt cost must be from 4..31");
         }
 
-        return new BCrypt().deriveRawKey(cost, salt, password);
+        return new BCrypt().deriveRawKey(cost, salt, pwInput);
     }
 }
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java
index 9af6758..1712db7 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/ECKeyPairGenerator.java
@@ -5,7 +5,6 @@
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
-import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
@@ -31,11 +30,6 @@
 
         this.random = ecP.getRandom();
         this.params = ecP.getDomainParameters();
-
-        if (this.random == null)
-        {
-            this.random = CryptoServicesRegistrar.getSecureRandom();
-        }
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java
index 7d4b90b..3ab1742 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/NaccacheSternKeyPairGenerator.java
@@ -15,7 +15,7 @@
 /**
  * Key generation parameters for NaccacheStern cipher. For details on this cipher, please see
  * 
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  */
 public class NaccacheSternKeyPairGenerator 
     implements AsymmetricCipherKeyPairGenerator 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/OpenBSDBCrypt.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/OpenBSDBCrypt.java
index 1c30065..c8cd361 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/OpenBSDBCrypt.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/OpenBSDBCrypt.java
@@ -40,6 +40,8 @@
     static
     {
         // Presently just the Bcrypt versions.
+        allowedVersions.add("2");
+        allowedVersions.add("2x");
         allowedVersions.add("2a");
         allowedVersions.add("2y");
         allowedVersions.add("2b");
@@ -55,48 +57,13 @@
         }
     }
 
-    public OpenBSDBCrypt()
+    private OpenBSDBCrypt()
     {
 
     }
 
     /**
      * Creates a 60 character Bcrypt String, including
-     * version, cost factor, salt and hash, separated by '$'
-     *
-     * @param version  the version, 2y,2b or 2a. (2a is not backwards compatible.)
-     * @param cost     the cost factor, treated as an exponent of 2
-     * @param salt     a 16 byte salt
-     * @param password the password
-     * @return a 60 character Bcrypt String
-     */
-    private static String createBcryptString(String version,
-                                             byte[] password,
-                                             byte[] salt,
-                                             int cost)
-    {
-        if (!allowedVersions.contains(version))
-        {
-            throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation.");
-        }
-
-        StringBuffer sb = new StringBuffer(60);
-        sb.append('$');
-        sb.append(version);
-        sb.append('$');
-        sb.append(cost < 10 ? ("0" + cost) : Integer.toString(cost));
-        sb.append('$');
-        sb.append(encodeData(salt));
-
-        byte[] key = BCrypt.generate(password, salt, cost);
-
-        sb.append(encodeData(key));
-
-        return sb.toString();
-    }
-
-    /**
-     * Creates a 60 character Bcrypt String, including
      * version, cost factor, salt and hash, separated by '$' using version
      * '2y'.
      *
@@ -113,6 +80,23 @@
         return generate(defaultVersion, password, salt, cost);
     }
 
+    /**
+      * Creates a 60 character Bcrypt String, including
+      * version, cost factor, salt and hash, separated by '$' using version
+      * '2y'.
+      *
+      * @param cost     the cost factor, treated as an exponent of 2
+      * @param salt     a 16 byte salt
+      * @param password the password
+      * @return a 60 character Bcrypt String
+      */
+     public static String generate(
+         byte[] password,
+         byte[] salt,
+         int cost)
+     {
+         return generate(defaultVersion, password, salt, cost);
+     }
 
     /**
      * Creates a 60 character Bcrypt String, including
@@ -130,15 +114,59 @@
         byte[] salt,
         int cost)
     {
+        if (password == null)
+        {
+            throw new IllegalArgumentException("Password required.");
+        }
+
+        return doGenerate(version, Strings.toUTF8ByteArray(password), salt, cost);
+    }
+
+    /**
+     * Creates a 60 character Bcrypt String, including
+     * version, cost factor, salt and hash, separated by '$'
+     *
+     * @param version  the version, may be 2b, 2y or 2a. (2a is not backwards compatible.)
+     * @param cost     the cost factor, treated as an exponent of 2
+     * @param salt     a 16 byte salt
+     * @param password the password already encoded as a byte array.
+     * @return a 60 character Bcrypt String
+     */
+    public static String generate(
+        String version,
+        byte[] password,
+        byte[] salt,
+        int cost)
+    {
+        if (password == null)
+        {
+            throw new IllegalArgumentException("Password required.");
+        }
+
+        return doGenerate(version, Arrays.clone(password), salt, cost);
+    }
+
+    /**
+     * Creates a 60 character Bcrypt String, including
+     * version, cost factor, salt and hash, separated by '$'
+     *
+     * @param version  the version, may be 2b, 2y or 2a. (2a is not backwards compatible.)
+     * @param cost     the cost factor, treated as an exponent of 2
+     * @param salt     a 16 byte salt
+     * @param psw the password
+     * @return a 60 character Bcrypt String
+     */
+    private static String doGenerate(
+        String version,
+        byte[] psw,
+        byte[] salt,
+        int cost)
+    {
         if (!allowedVersions.contains(version))
         {
             throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation.");
         }
 
-        if (password == null)
-        {
-            throw new IllegalArgumentException("Password required.");
-        }
         if (salt == null)
         {
             throw new IllegalArgumentException("Salt required.");
@@ -152,8 +180,6 @@
             throw new IllegalArgumentException("Invalid cost factor.");
         }
 
-        byte[] psw = Strings.toUTF8ByteArray(password);
-
         // 0 termination:
 
         byte[] tmp = new byte[psw.length >= 72 ? 72 : psw.length + 1];
@@ -190,21 +216,97 @@
         String bcryptString,
         char[] password)
     {
+        if (password == null)
+        {
+            throw new IllegalArgumentException("Missing password.");
+        }
+
+        return doCheckPassword(bcryptString, Strings.toUTF8ByteArray(password));
+    }
+
+    /**
+     * Checks if a password corresponds to a 60 character Bcrypt String
+     *
+     * @param bcryptString a 60 character Bcrypt String, including
+     *                     version, cost factor, salt and hash,
+     *                     separated by '$'
+     * @param password     the password as an array of bytes
+     * @return true if the password corresponds to the
+     * Bcrypt String, otherwise false
+     */
+    public static boolean checkPassword(
+        String bcryptString,
+        byte[] password)
+    {
+        if (password == null)
+        {
+            throw new IllegalArgumentException("Missing password.");
+        }
+
+        return doCheckPassword(bcryptString, Arrays.clone(password));
+    }
+
+    /**
+     * Checks if a password corresponds to a 60 character Bcrypt String
+     *
+     * @param bcryptString a 60 character Bcrypt String, including
+     *                     version, cost factor, salt and hash,
+     *                     separated by '$'
+     * @param password     the password as an array of chars
+     * @return true if the password corresponds to the
+     * Bcrypt String, otherwise false
+     */
+    private static boolean doCheckPassword(
+        String bcryptString,
+        byte[] password)
+    {
+        if (bcryptString == null)
+        {
+            throw new IllegalArgumentException("Missing bcryptString.");
+        }
+
+        if (bcryptString.charAt(1) != '2')   // check for actual Bcrypt type.
+        {
+            throw new IllegalArgumentException("not a Bcrypt string");
+        }
+
         // validate bcryptString:
-        if (bcryptString.length() != 60)
+        final int sLength = bcryptString.length();
+        if (sLength != 60 && !(sLength == 59 && bcryptString.charAt(2) == '$'))   // check for $2$
         {
-            throw new DataLengthException("Bcrypt String length: "
-                + bcryptString.length() + ", 60 required.");
+            throw new DataLengthException("Bcrypt String length: " + sLength + ", 60 required.");
         }
 
-        if (bcryptString.charAt(0) != '$'
-            || bcryptString.charAt(3) != '$'
-            || bcryptString.charAt(6) != '$')
+        if (bcryptString.charAt(2) == '$')
         {
-            throw new IllegalArgumentException("Invalid Bcrypt String format.");
+            if (bcryptString.charAt(0) != '$'
+                || bcryptString.charAt(5) != '$')
+            {
+                throw new IllegalArgumentException("Invalid Bcrypt String format.");
+            }
+        }
+        else
+        {
+            if (bcryptString.charAt(0) != '$'
+                || bcryptString.charAt(3) != '$'
+                || bcryptString.charAt(6) != '$')
+            {
+                throw new IllegalArgumentException("Invalid Bcrypt String format.");
+            }
         }
 
-        String version = bcryptString.substring(1, 3);
+        String version;
+        int base;
+        if (bcryptString.charAt(2) == '$')
+        {
+            version = bcryptString.substring(1, 2);
+            base = 3;
+        }
+        else
+        {
+            version = bcryptString.substring(1, 3);
+            base = 4;
+        }
 
         if (!allowedVersions.contains(version))
         {
@@ -212,7 +314,7 @@
         }
 
         int cost = 0;
-        String costStr = bcryptString.substring(4, 6);
+        String costStr = bcryptString.substring(base, base + 2);
         try
         {
             cost = Integer.parseInt(costStr);
@@ -226,17 +328,53 @@
             throw new IllegalArgumentException("Invalid cost factor: " + cost + ", 4 < cost < 31 expected.");
         }
         // check password:
-        if (password == null)
-        {
-            throw new IllegalArgumentException("Missing password.");
-        }
         byte[] salt = decodeSaltString(
             bcryptString.substring(bcryptString.lastIndexOf('$') + 1,
-                bcryptString.length() - 31));
+                sLength - 31));
 
-        String newBcryptString = generate(version, password, salt, cost);
+        String newBcryptString = doGenerate(version, password, salt, cost);
 
-        return bcryptString.equals(newBcryptString);
+        boolean isEqual = sLength == newBcryptString.length();
+        for (int i = 0; i != sLength; i++)
+        {
+            isEqual &= (bcryptString.charAt(i) == newBcryptString.charAt(i));
+        }
+        return isEqual;
+    }
+
+    /**
+     * Creates a 60 character Bcrypt String, including
+     * version, cost factor, salt and hash, separated by '$'
+     *
+     * @param version  the version, 2y,2b or 2a. (2a is not backwards compatible.)
+     * @param cost     the cost factor, treated as an exponent of 2
+     * @param salt     a 16 byte salt
+     * @param password the password
+     * @return a 60 character Bcrypt String
+     */
+    private static String createBcryptString(String version,
+                                             byte[] password,
+                                             byte[] salt,
+                                             int cost)
+    {
+        if (!allowedVersions.contains(version))
+        {
+            throw new IllegalArgumentException("Version " + version + " is not accepted by this implementation.");
+        }
+        
+        StringBuilder sb = new StringBuilder(60);
+        sb.append('$');
+        sb.append(version);
+        sb.append('$');
+        sb.append(cost < 10 ? ("0" + cost) : Integer.toString(cost));
+        sb.append('$');
+        encodeData(sb, salt);
+
+        byte[] key = BCrypt.generate(password, salt, cost);
+
+        encodeData(sb, key);
+
+        return sb.toString();
     }
 
     /*
@@ -245,9 +383,9 @@
      * @param 	a byte representation of the salt or the password
      * @return 	the Bcrypt base64 String
      */
-    private static String encodeData(
+    private static void encodeData(
+        StringBuilder sb,
         byte[] data)
-
     {
         if (data.length != 24 && data.length != 16) // 192 bit key or 128 bit salt expected
         {
@@ -266,7 +404,6 @@
             data[data.length - 1] = (byte)0;
         }
 
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
         int len = data.length;
 
         int a1, a2, a3;
@@ -274,27 +411,25 @@
         for (i = 0; i < len; i += 3)
         {
             a1 = data[i] & 0xff;
-            a2 = data[i + 1] & 0xff;
-            a3 = data[i + 2] & 0xff;
+            a2 = data[i + 1] & 0xff;    // lgtm [java/index-out-of-bounds]
+            a3 = data[i + 2] & 0xff;    // lgtm [java/index-out-of-bounds]
 
-            out.write(encodingTable[(a1 >>> 2) & 0x3f]);
-            out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
-            out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
-            out.write(encodingTable[a3 & 0x3f]);
+            sb.append((char)encodingTable[(a1 >>> 2) & 0x3f]);
+            sb.append((char)encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
+            sb.append((char)encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
+            sb.append((char)encodingTable[a3 & 0x3f]);
         }
 
-        String result = Strings.fromByteArray(out.toByteArray());
         if (salt == true)// truncate padding
         {
-            return result.substring(0, 22);
+            sb.setLength(sb.length() - 2);
         }
         else
         {
-            return result.substring(0, result.length() - 1);
+            sb.setLength(sb.length() -1);
         }
     }
 
-
     /*
      * decodes the bcrypt base 64 encoded SaltString
      *
@@ -337,10 +472,11 @@
 
         for (int i = 0; i < len; i += 4)
         {
+            // suppress LGTM warnings index-out-of-bounds since the loop increments i by 4
             b1 = decodingTable[saltChars[i]];
-            b2 = decodingTable[saltChars[i + 1]];
-            b3 = decodingTable[saltChars[i + 2]];
-            b4 = decodingTable[saltChars[i + 3]];
+            b2 = decodingTable[saltChars[i + 1]];   // lgtm [java/index-out-of-bounds]
+            b3 = decodingTable[saltChars[i + 2]];   // lgtm [java/index-out-of-bounds]
+            b4 = decodingTable[saltChars[i + 3]];   // lgtm [java/index-out-of-bounds]
 
             out.write((b1 << 2) | (b2 >> 4));
             out.write((b2 << 4) | (b3 >> 2));
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
index d9b82c3..eb2756d 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS12ParametersGenerator.java
@@ -11,7 +11,7 @@
  * Generator for PBE derived keys and ivs as defined by PKCS 12 V1.0.
  * <p>
  * The document this implementation is based on can be found at
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-12/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-12/index.html>
  * RSA's PKCS12 Page</a>
  */
 public class PKCS12ParametersGenerator
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java
index 1c62ecc..758a3f9 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S1ParametersGenerator.java
@@ -12,7 +12,7 @@
  * digest used to drive it.
  * <p>
  * The document this implementation is based on can be found at
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
  * RSA's PKCS5 Page</a>
  */
 public class PKCS5S1ParametersGenerator
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
index 383872f..4799248 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/PKCS5S2ParametersGenerator.java
@@ -14,7 +14,7 @@
  * This generator uses a SHA-1 HMac as the calculation function.
  * <p>
  * The document this implementation is based on can be found at
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
  * RSA's PKCS5 Page</a>
  */
 public class PKCS5S2ParametersGenerator
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java
index eadbaa6..6d5fc97 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/RSAKeyPairGenerator.java
@@ -139,7 +139,7 @@
 
             dP = d.remainder(pSub1);
             dQ = d.remainder(qSub1);
-            qInv = q.modInverse(p);
+            qInv = BigIntegers.modOddInverse(p, q);
 
             result = new AsymmetricCipherKeyPair(
                 new RSAKeyParameters(false, n, e),
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java b/bcprov/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java
index 5097a36..85e1cb8 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/generators/SCrypt.java
@@ -5,6 +5,7 @@
 import org.bouncycastle.crypto.engines.Salsa20Engine;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.Pack;
 
 /**
@@ -83,11 +84,22 @@
 
             Pack.littleEndianToInt(bytes, 0, B);
 
+            /*
+             * Chunk memory allocations; We choose 'd' so that there will be 2**d chunks, each not
+             * larger than 32KiB, except that the minimum chunk size is 2 * r * 32.
+             */
+            int d = 0, total = N * r;
+            while ((N - d) > 2 && total > (1 << 10))
+            {
+                ++d;
+                total >>>= 1;
+            }
+
             int MFLenWords = MFLenBytes >>> 2;
             for (int BOff = 0; BOff < BLen; BOff += MFLenWords)
             {
                 // TODO These can be done in parallel threads
-                SMix(B, BOff, N, r);
+                SMix(B, BOff, N, d, r);
             }
 
             Pack.intToLittleEndian(B, bytes, 0);
@@ -109,8 +121,12 @@
         return key.getKey();
     }
 
-    private static void SMix(int[] B, int BOff, int N, int r)
+    private static void SMix(int[] B, int BOff, int N, int d, int r)
     {
+        int powN = Integers.numberOfTrailingZeros(N);
+        int blocksPerChunk = N >>> d;
+        int chunkCount = 1 << d, chunkMask = blocksPerChunk - 1, chunkPow = powN - d;
+
         int BCount = r * 32;
 
         int[] blockX1 = new int[16];
@@ -118,28 +134,36 @@
         int[] blockY = new int[BCount];
 
         int[] X = new int[BCount];
-        int[] V = new int[N * BCount];
+        int[][] VV = new int[chunkCount][];
 
         try
         {
             System.arraycopy(B, BOff, X, 0, BCount);
 
-            int off = 0;
-            for (int i = 0; i < N; i += 2)
+            for (int c = 0; c < chunkCount; ++c)
             {
-                System.arraycopy(X, 0, V, off, BCount);
-                off += BCount;
-                BlockMix(X, blockX1, blockX2, blockY, r);
-                System.arraycopy(blockY, 0, V, off, BCount);
-                off += BCount;
-                BlockMix(blockY, blockX1, blockX2, X, r);
+                int[] V = new int[blocksPerChunk * BCount];
+                VV[c] = V;
+
+                int off = 0;
+                for (int i = 0; i < blocksPerChunk; i += 2)
+                {
+                    System.arraycopy(X, 0, V, off, BCount);
+                    off += BCount;
+                    BlockMix(X, blockX1, blockX2, blockY, r);
+                    System.arraycopy(blockY, 0, V, off, BCount);
+                    off += BCount;
+                    BlockMix(blockY, blockX1, blockX2, X, r);
+                }
             }
 
             int mask = N - 1;
             for (int i = 0; i < N; ++i)
             {
                 int j = X[BCount - 16] & mask;
-                System.arraycopy(V, j * BCount, blockY, 0, BCount);
+                int[] V = VV[j >>> chunkPow];
+                int VOff = (j & chunkMask) * BCount;
+                System.arraycopy(V, VOff, blockY, 0, BCount);
                 Xor(blockY, X, 0, blockY);
                 BlockMix(blockY, blockX1, blockX2, X, r);
             }
@@ -148,7 +172,7 @@
         }
         finally
         {
-            Clear(V);
+            ClearAll(VV);
             ClearAll(new int[][]{X, blockX1, blockX2, blockY});
         }
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java b/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java
index 71b29a3..927c2ec 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/kems/ECIESKeyEncapsulation.java
@@ -205,7 +205,7 @@
         BigInteger xHat = ecPrivKey.getD();
         if (CofactorMode)
         {
-            xHat = xHat.multiply(h.modInverse(n)).mod(n);
+            xHat = xHat.multiply(ecParams.getHInv()).mod(n);
         }
 
         ECPoint hTilde = gHat.multiply(xHat).normalize();
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/KMAC.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/KMAC.java
new file mode 100644
index 0000000..ba7d82c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/KMAC.java
@@ -0,0 +1,204 @@
+package org.bouncycastle.crypto.macs;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.Xof;
+import org.bouncycastle.crypto.digests.CSHAKEDigest;
+import org.bouncycastle.crypto.digests.XofUtils;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
+/**
+ * KMAC - MAC with optional XOF mode.
+ * <p>
+ * From NIST Special Publication 800-185 - SHA-3 Derived Functions:cSHAKE, KMAC, TupleHash and ParallelHash
+ * </p>
+ */
+public class KMAC
+    implements Mac, Xof
+{
+    private static final byte[] padding = new byte[100];
+
+    private final CSHAKEDigest cshake;
+    private final int bitLength;
+    private final int outputLength;
+
+    private byte[] key;
+    private boolean initialised;
+    private boolean firstOutput;
+
+    /**
+     * Base constructor.
+     *
+     * @param bitLength bit length of the underlying SHAKE function, 128 or 256.
+     * @param S         the customization string - available for local use.
+     */
+    public KMAC(int bitLength, byte[] S)
+    {
+        this.cshake = new CSHAKEDigest(bitLength, Strings.toByteArray("KMAC"), S);
+        this.bitLength = bitLength;
+        this.outputLength = bitLength * 2 / 8;
+    }
+
+    public void init(CipherParameters params)
+        throws IllegalArgumentException
+    {
+        KeyParameter kParam = (KeyParameter)params;
+
+        this.key = Arrays.clone(kParam.getKey());
+        this.initialised = true;
+
+        reset();
+    }
+
+    public String getAlgorithmName()
+    {
+        return "KMAC" + cshake.getAlgorithmName().substring(6);
+    }
+
+    public int getByteLength()
+    {
+        return cshake.getByteLength();
+    }
+
+    public int getMacSize()
+    {
+        return outputLength;
+    }
+
+    public int getDigestSize()
+    {
+        return outputLength;
+    }
+
+    public void update(byte in)
+        throws IllegalStateException
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException("KMAC not initialized");
+        }
+
+        cshake.update(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+        throws DataLengthException, IllegalStateException
+    {
+        if (!initialised)
+        {
+            throw new IllegalStateException("KMAC not initialized");
+        }
+
+        cshake.update(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        if (firstOutput)
+        {
+            if (!initialised)
+            {
+                throw new IllegalStateException("KMAC not initialized");
+            }
+
+            byte[] encOut = XofUtils.rightEncode(getMacSize() * 8);
+
+            cshake.update(encOut, 0, encOut.length);
+        }
+        
+        int rv = cshake.doFinal(out, outOff, getMacSize());
+
+        reset();
+
+        return rv;
+    }
+
+    public int doFinal(byte[] out, int outOff, int outLen)
+    {
+        if (firstOutput)
+        {
+            if (!initialised)
+            {
+                throw new IllegalStateException("KMAC not initialized");
+            }
+
+            byte[] encOut = XofUtils.rightEncode(outLen * 8);
+
+            cshake.update(encOut, 0, encOut.length);
+        }
+        
+        int rv = cshake.doFinal(out, outOff, outLen);
+
+        reset();
+
+        return rv;
+    }
+
+    public int doOutput(byte[] out, int outOff, int outLen)
+    {
+        if (firstOutput)
+        {
+            if (!initialised)
+            {
+                throw new IllegalStateException("KMAC not initialized");
+            }
+
+            byte[] encOut = XofUtils.rightEncode(0);
+
+            cshake.update(encOut, 0, encOut.length);
+
+            firstOutput = false;
+        }
+
+        return cshake.doOutput(out, outOff, outLen);
+    }
+
+    public void reset()
+    {
+        cshake.reset();
+
+        if (key != null)
+        {
+            if (bitLength == 128)
+            {
+                bytePad(key, 168);
+            }
+            else
+            {
+                bytePad(key, 136);
+            }
+        }
+
+        firstOutput = true;
+    }
+
+    private void bytePad(byte[] X, int w)
+    {
+        byte[] bytes = XofUtils.leftEncode(w);
+        update(bytes, 0, bytes.length);
+        byte[] encX = encode(X);
+        update(encX, 0, encX.length);
+
+        int required = w - ((bytes.length + encX.length) % w);
+
+        if (required > 0 && required != w)
+        {
+            while (required > padding.length)
+            {
+                update(padding, 0, padding.length);
+                required -= padding.length;
+            }
+
+            update(padding, 0, required);
+        }
+    }
+
+    private static byte[] encode(byte[] X)
+    {
+        return Arrays.concatenate(XofUtils.leftEncode(X.length * 8), X);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/SipHash128.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/SipHash128.java
new file mode 100644
index 0000000..ae49d03
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/SipHash128.java
@@ -0,0 +1,85 @@
+package org.bouncycastle.crypto.macs;
+
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.util.Pack;
+
+/**
+ * Implementation of SipHash with 128 bit output.
+ * <p>
+ * Based on the {@link SipHash} and the C reference implementation
+ * https://github.com/veorq/SipHash.
+ * 
+ */
+public class SipHash128
+    extends SipHash
+{
+
+    /**
+     * SipHash128-2-4
+     */
+    public SipHash128()
+    {
+      super();
+    }
+
+    /**
+     * SipHash128-c-d
+     *
+     * @param c the number of compression rounds
+     * @param d the number of finalization rounds
+     */
+    public SipHash128(int c, int d)
+    {
+       super(c, d);
+    }
+
+    public String getAlgorithmName()
+    {
+        return "SipHash128-" + c + "-" + d;
+    }
+
+    public int getMacSize()
+    {
+        return 16;
+    }
+
+    public long doFinal()
+        throws DataLengthException, IllegalStateException {
+        throw new UnsupportedOperationException("doFinal() is not supported");
+    }
+
+    public int doFinal(byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException
+    {
+        // NOTE: 2 distinct shifts to avoid "64-bit shift" when wordPos == 0
+        m >>>= ((7 - wordPos) << 3);
+        m >>>= 8;
+        m |= (((wordCount << 3) + wordPos) & 0xffL) << 56;
+
+        processMessageWord();
+
+        v2 ^= 0xeeL;
+
+        applySipRounds(d);
+
+        long r0 = v0 ^ v1 ^ v2 ^ v3;
+
+        v1 ^= 0xddL;
+        applySipRounds(d);
+
+        long r1 = v0 ^ v1 ^ v2 ^ v3;
+
+        reset();
+
+        Pack.longToLittleEndian(r0, out, outOff);
+        Pack.longToLittleEndian(r1, out, outOff + 8);
+        return 16;
+    }
+
+    public void reset()
+    {
+        super.reset();
+        v1 ^= 0xeeL;
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc128Mac.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc128Mac.java
index d50669c..199ac23 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc128Mac.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc128Mac.java
@@ -6,7 +6,7 @@
 
 /**
  * Zuc128 Mac implementation.
- * Based on http://www.qtc.jp/3GPP/Specs/eea3eia3specificationv16.pdf
+ * Based on https://www.qtc.jp/3GPP/Specs/eea3eia3specificationv16.pdf
  */
 public final class Zuc128Mac
     implements Mac
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc256Mac.java b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc256Mac.java
index 6d45b1f..26313d5 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc256Mac.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/macs/Zuc256Mac.java
@@ -6,7 +6,7 @@
 
 /**
  * Zuc256 Mac implementation.
- * Based on http://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
+ * Based on https://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf
  */
 public final class Zuc256Mac
     implements Mac
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java
index 6167d25..b11716f 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java
@@ -36,6 +36,11 @@
     {
         super(cipher);
 
+        if (bitBlockSize > (cipher.getBlockSize() * 8) || bitBlockSize < 8 || bitBlockSize % 8 != 0)
+        {
+            throw new IllegalArgumentException("CFB" + bitBlockSize + " not supported");
+        }
+
         this.cipher = cipher;
         this.blockSize = bitBlockSize / 8;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/ChaCha20Poly1305.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/ChaCha20Poly1305.java
index 1ac4576..fdcb48b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/ChaCha20Poly1305.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/ChaCha20Poly1305.java
@@ -175,7 +175,7 @@
         case State.ENC_DATA:
             return total + MAC_SIZE;
         default:
-            throw new IllegalStateException("state="+state);
+            throw new IllegalStateException();
         }
     }
 
@@ -281,9 +281,15 @@
         {
             throw new NullPointerException("'in' cannot be null");
         }
+        /*
+         * The BC provider can pass null when it expects no output (e.g. based on a
+         * getUpdateOutputSize call).
+         * 
+         * See https://github.com/bcgit/bc-java/issues/674
+         */
         if (null == out)
         {
-            throw new NullPointerException("'out' cannot be null");
+//            throw new NullPointerException("'out' cannot be null");
         }
         if (inOff < 0)
         {
@@ -542,7 +548,7 @@
 
     private void padMAC(long count)
     {
-        int partial = (int)count % MAC_SIZE;
+        int partial = (int)count & (MAC_SIZE - 1);
         if (0 != partial)
         {
             poly1305.update(ZEROES, 0, MAC_SIZE - partial);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java
index 3bb275b..18efa0a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java
@@ -15,7 +15,7 @@
  * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and
  * Efficiency - by M. Bellare, P. Rogaway, D. Wagner.
  *
- * http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
+ * https://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
  *
  * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block
  * cipher to encrypt and authenticate data. It's on-line (the length of a
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CBCBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CBCBlockCipher.java
index 97ba1f9..ca19736 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CBCBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CBCBlockCipher.java
@@ -8,7 +8,7 @@
 
 /**
  * An implementation of the CBC mode for GOST 3412 2015 cipher.
- * See  <a href="http://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
+ * See  <a href="https://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
  */
 public class G3413CBCBlockCipher
     implements BlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CFBBlockCipher.java
index 34afce2..c356437 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413CFBBlockCipher.java
@@ -9,7 +9,7 @@
 
 /**
  * An implementation of the CFB mode for GOST 3412 2015 cipher.
- * See  <a href="http://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
+ * See  <a href="https://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
  */
 public class G3413CFBBlockCipher
     extends StreamBlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413OFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413OFBBlockCipher.java
index 148f382..a9a0faf 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413OFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/G3413OFBBlockCipher.java
@@ -9,7 +9,7 @@
 
 /**
  * An implementation of the OFB mode for GOST 3412 2015 cipher.
- * See  <a href="http://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
+ * See  <a href="https://www.tc26.ru/standard/gost/GOST_R_3413-2015.pdf">GOST R 3413 2015</a>
  */
 public class G3413OFBBlockCipher
     extends StreamBlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
index 7865380..c6641fc 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
@@ -13,10 +13,10 @@
 import org.bouncycastle.util.Arrays;
 
 /**
- * An implementation of <a href="http://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
+ * An implementation of <a href="https://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
  * Authenticated-Encryption Algorithm</a>, licensed per:
  * <p>
- * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for
+ * <blockquote> <a href="https://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for
  * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) &mdash; &ldquo;License 1&rdquo; <br>
  * Under this license, you are authorized to make, use, and distribute open-source software
  * implementations of OCB. This license terminates for you if you sue someone over their open-source
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java
index d9ff428..3cebc01 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java
@@ -25,16 +25,21 @@
      *
      * @param cipher the block cipher to be used as the basis of the
      * feedback mode.
-     * @param blockSize the block size in bits (note: a multiple of 8)
+     * @param bitBlockSize the block size in bits (note: a multiple of 8)
      */
     public OFBBlockCipher(
         BlockCipher cipher,
-        int         blockSize)
+        int         bitBlockSize)
     {
         super(cipher);
 
+        if (bitBlockSize > (cipher.getBlockSize() * 8) || bitBlockSize < 8 || bitBlockSize % 8 != 0)
+        {
+            throw new IllegalArgumentException("0FB" + bitBlockSize + " not supported");
+        }
+
         this.cipher = cipher;
-        this.blockSize = blockSize / 8;
+        this.blockSize = bitBlockSize / 8;
 
         this.IV = new byte[cipher.getBlockSize()];
         this.ofbV = new byte[cipher.getBlockSize()];
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java
index 8ae1588..99f96b2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java
@@ -11,7 +11,7 @@
  * to the data stream already, and just accomodates the reset after
  * (blockSize + 2) bytes have been read.
  * <p>
- * For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
+ * For further info see <a href="https://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
  */
 public class OpenPGPCFBBlockCipher
     implements BlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
index 72f68ca..b9739a0 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
@@ -7,7 +7,7 @@
 import org.bouncycastle.crypto.params.ParametersWithIV;
 
 /**
- * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode on top of a simple cipher. For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
+ * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode on top of a simple cipher. For further info see <a href="https://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
  */
 public class PGPCFBBlockCipher
     implements BlockCipher
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
index 3e24a15..4d13820 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.modes.gcm;
 
 import org.bouncycastle.math.raw.Interleave;
+import org.bouncycastle.util.Longs;
 import org.bouncycastle.util.Pack;
 
 public abstract class GCMUtil
@@ -140,24 +141,58 @@
 
     public static void multiply(long[] x, long[] y)
     {
+//        long x0 = x[0], x1 = x[1];
+//        long y0 = y[0], y1 = y[1];
+//        long z0 = 0, z1 = 0, z2 = 0;
+//
+//        for (int j = 0; j < 64; ++j)
+//        {
+//            long m0 = x0 >> 63; x0 <<= 1;
+//            z0 ^= (y0 & m0);
+//            z1 ^= (y1 & m0);
+//
+//            long m1 = x1 >> 63; x1 <<= 1;
+//            z1 ^= (y0 & m1);
+//            z2 ^= (y1 & m1);
+//
+//            long c = (y1 << 63) >> 8;
+//            y1 = (y1 >>> 1) | (y0 << 63);
+//            y0 = (y0 >>> 1) ^ (c & E1L);
+//        }
+//
+//        z0 ^= z2 ^ (z2 >>>  1) ^ (z2 >>>  2) ^ (z2 >>>  7);
+//        z1 ^=      (z2 <<  63) ^ (z2 <<  62) ^ (z2 <<  57);
+//
+//        x[0] = z0;
+//        x[1] = z1;
+
+        /*
+         * "Three-way recursion" as described in "Batch binary Edwards", Daniel J. Bernstein.
+         *
+         * Without access to the high part of a 64x64 product x * y, we use a bit reversal to calculate it:
+         *     rev(x) * rev(y) == rev((x * y) << 1) 
+         */
+
         long x0 = x[0], x1 = x[1];
         long y0 = y[0], y1 = y[1];
-        long z0 = 0, z1 = 0, z2 = 0;
+        long x0r = Longs.reverse(x0), x1r = Longs.reverse(x1);
+        long y0r = Longs.reverse(y0), y1r = Longs.reverse(y1);
 
-        for (int j = 0; j < 64; ++j)
-        {
-            long m0 = x0 >> 63; x0 <<= 1;
-            z0 ^= (y0 & m0);
-            z1 ^= (y1 & m0);
+        long h0  = Longs.reverse(implMul64(x0r, y0r));
+        long h1  = implMul64(x0, y0) << 1;
+        long h2  = Longs.reverse(implMul64(x1r, y1r));
+        long h3  = implMul64(x1, y1) << 1;
+        long h4  = Longs.reverse(implMul64(x0r ^ x1r, y0r ^ y1r));
+        long h5  = implMul64(x0 ^ x1, y0 ^ y1) << 1;
 
-            long m1 = x1 >> 63; x1 <<= 1;
-            z1 ^= (y0 & m1);
-            z2 ^= (y1 & m1);
+        long z0  = h0;
+        long z1  = h1 ^ h0 ^ h2 ^ h4;
+        long z2  = h2 ^ h1 ^ h3 ^ h5;
+        long z3  = h3;
 
-            long c = (y1 << 63) >> 8;
-            y1 = (y1 >>> 1) | (y0 << 63);
-            y0 = (y0 >>> 1) ^ (c & E1L);
-        }
+        z1 ^= z3 ^ (z3 >>>  1) ^ (z3 >>>  2) ^ (z3 >>>  7);
+//      z2 ^=      (z3 <<  63) ^ (z3 <<  62) ^ (z3 <<  57);
+        z2 ^=                    (z3 <<  62) ^ (z3 <<  57);
 
         z0 ^= z2 ^ (z2 >>>  1) ^ (z2 >>>  2) ^ (z2 >>>  7);
         z1 ^=      (z2 <<  63) ^ (z2 <<  62) ^ (z2 <<  57);
@@ -382,4 +417,29 @@
         z[0] = x[0] ^ y[0];
         z[1] = x[1] ^ y[1];
     }
+
+    private static long implMul64(long x, long y)
+    {
+        long x0 = x & 0x1111111111111111L;
+        long x1 = x & 0x2222222222222222L;
+        long x2 = x & 0x4444444444444444L;
+        long x3 = x & 0x8888888888888888L;
+
+        long y0 = y & 0x1111111111111111L;
+        long y1 = y & 0x2222222222222222L;
+        long y2 = y & 0x4444444444444444L;
+        long y3 = y & 0x8888888888888888L;
+
+        long z0 = (x0 * y0) ^ (x1 * y3) ^ (x2 * y2) ^ (x3 * y1);
+        long z1 = (x0 * y1) ^ (x1 * y0) ^ (x2 * y3) ^ (x3 * y2);
+        long z2 = (x0 * y2) ^ (x1 * y1) ^ (x2 * y0) ^ (x3 * y3);
+        long z3 = (x0 * y3) ^ (x1 * y2) ^ (x2 * y1) ^ (x3 * y0);
+
+        z0 &= 0x1111111111111111L;
+        z1 &= 0x2222222222222222L;
+        z2 &= 0x4444444444444444L;
+        z3 &= 0x8888888888888888L;
+
+        return z0 | z1 | z2 | z3;
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java b/bcprov/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java
index 076ad58..0ad1e38 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/paddings/ISO10126d2Padding.java
@@ -21,14 +21,7 @@
     public void init(SecureRandom random)
         throws IllegalArgumentException
     {
-        if (random != null)
-        {
-            this.random = random;
-        }
-        else
-        {
-            this.random = CryptoServicesRegistrar.getSecureRandom();
-        }
+        this.random = CryptoServicesRegistrar.getSecureRandom(random);
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/DESParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/DESParameters.java
index 5bee360..061d134 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/DESParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/DESParameters.java
@@ -52,7 +52,7 @@
      * if the given DES key material is weak or semi-weak.
      * Key material that is too short is regarded as weak.
      * <p>
-     * See <a href="http://www.counterpane.com/applied.html">"Applied
+     * See <a href="https://www.counterpane.com/applied.html">"Applied
      * Cryptography"</a> by Bruce Schneier for more information.
      *
      * @return true if the given DES key material is weak or semi-weak,
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java
index ba392ab..dce2dbd 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/DHPublicKeyParameters.java
@@ -2,6 +2,9 @@
 
 import java.math.BigInteger;
 
+import org.bouncycastle.math.raw.Nat;
+import org.bouncycastle.util.Integers;
+
 public class DHPublicKeyParameters
     extends DHKeyParameters
 {
@@ -26,25 +29,39 @@
             throw new NullPointerException("y value cannot be null");
         }
 
+        BigInteger p = dhParams.getP();
+
         // TLS check
-        if (y.compareTo(TWO) < 0 || y.compareTo(dhParams.getP().subtract(TWO)) > 0)
+        if (y.compareTo(TWO) < 0 || y.compareTo(p.subtract(TWO)) > 0)
         {
             throw new IllegalArgumentException("invalid DH public key");
         }
 
-        if (dhParams.getQ() != null)
-        {
-            if (ONE.equals(y.modPow(dhParams.getQ(), dhParams.getP())))
-            {
-                return y;
-            }
-
-            throw new IllegalArgumentException("Y value does not appear to be in correct group");
-        }
-        else
+        BigInteger q = dhParams.getQ();
+        if (q == null)
         {
             return y;         // we can't validate without Q.
         }
+
+        if (p.testBit(0)
+            && p.bitLength() - 1 == q.bitLength()
+            && p.shiftRight(1).equals(q))
+        {
+            // Safe prime case
+            if (1 == legendre(y, p))
+            {
+                return y;
+            }
+        }
+        else
+        {
+            if (ONE.equals(y.modPow(q, p)))
+            {
+                return y;
+            }
+        }
+
+        throw new IllegalArgumentException("Y value does not appear to be in correct group");
     }
 
     public BigInteger getY()
@@ -69,4 +86,79 @@
 
         return other.getY().equals(y) && super.equals(obj);
     }
+
+    private static int legendre(BigInteger a, BigInteger b)
+    {
+//        int r = 0, bits = b.intValue();
+//
+//        for (;;)
+//        {
+//            int lowestSetBit = a.getLowestSetBit();
+//            a = a.shiftRight(lowestSetBit);
+//            r ^= (bits ^ (bits >>> 1)) & (lowestSetBit << 1);
+//
+//            int cmp = a.compareTo(b);
+//            if (cmp == 0)
+//            {
+//                break;
+//            }
+//
+//            if (cmp < 0)
+//            {
+//                BigInteger t = a; a = b; b = t;
+//
+//                int oldBits = bits;
+//                bits = b.intValue();
+//                r ^= oldBits & bits;
+//            }
+//
+//            a = a.subtract(b);
+//        }
+//
+//        return ONE.equals(b) ? (1 - (r & 2)) : 0;
+
+        int bitLength = b.bitLength();
+        int[] A = Nat.fromBigInteger(bitLength, a);
+        int[] B = Nat.fromBigInteger(bitLength, b);
+
+        int r = 0;
+
+        int len = B.length;
+        for (;;)
+        {
+            while (A[0] == 0)
+            {
+                Nat.shiftDownWord(len, A, 0);
+            }
+
+            int shift = Integers.numberOfTrailingZeros(A[0]);
+            if (shift > 0)
+            {
+                Nat.shiftDownBits(len, A, shift, 0);
+                int bits = B[0];
+                r ^= (bits ^ (bits >>> 1)) & (shift << 1);
+            }
+
+            int cmp = Nat.compare(len, A, B);
+            if (cmp == 0)
+            {
+                break;
+            }
+
+            if (cmp < 0)
+            {
+                r ^= A[0] & B[0];
+                int[] t = A; A = B; B = t;
+            }
+
+            while (A[len - 1] == 0)
+            {
+                len = len - 1;
+            }
+
+            Nat.sub(len, A, B, A);
+        }
+
+        return Nat.isOne(len, B) ? (1 - (r & 2)) : 0;
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java
index 1a6fe18..6e84496 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECDomainParameters.java
@@ -2,11 +2,13 @@
 
 import java.math.BigInteger;
 
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.math.ec.ECAlgorithms;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
 
 public class ECDomainParameters
     implements ECConstants
@@ -19,6 +21,11 @@
 
     private BigInteger  hInv = null;
 
+    public ECDomainParameters(X9ECParameters x9)
+    {
+        this(x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+    }
+
     public ECDomainParameters(
         ECCurve     curve,
         ECPoint     G,
@@ -84,7 +91,7 @@
     {
         if (hInv == null)
         {
-            hInv = h.modInverse(n);
+            hInv = BigIntegers.modOddInverseVar(n, h);
         }
         return hInv;
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECGOST3410Parameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECGOST3410Parameters.java
index e90d30f..7872192 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECGOST3410Parameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECGOST3410Parameters.java
@@ -16,7 +16,7 @@
 
     public ECGOST3410Parameters(ECDomainParameters ecParameters, ASN1ObjectIdentifier publicKeyParamSet, ASN1ObjectIdentifier digestParamSet, ASN1ObjectIdentifier encryptionParamSet)
     {
-        super(publicKeyParamSet, ecParameters.getCurve(), ecParameters.getG(), ecParameters.getN(), ecParameters.getH(), ecParameters.getSeed());
+        super(publicKeyParamSet, ecParameters);
 
         if (ecParameters instanceof ECNamedDomainParameters)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECNamedDomainParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECNamedDomainParameters.java
index ed89589..1394884 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ECNamedDomainParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ECNamedDomainParameters.java
@@ -3,6 +3,7 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
 import org.bouncycastle.math.ec.ECPoint;
@@ -35,6 +36,12 @@
         this.name = name;
     }
 
+    public ECNamedDomainParameters(ASN1ObjectIdentifier name, X9ECParameters x9)
+    {
+        super(x9);
+        this.name = name;
+    }
+
     public ASN1ObjectIdentifier getName()
     {
         return name;
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed25519PrivateKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed25519PrivateKeyParameters.java
index d5d3ed5..56d00d3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed25519PrivateKeyParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed25519PrivateKeyParameters.java
@@ -17,6 +17,8 @@
 
     private final byte[] data = new byte[KEY_SIZE];
 
+    private Ed25519PublicKeyParameters cachedPublicKey;
+
     public Ed25519PrivateKeyParameters(SecureRandom random)
     {
         super(true);
@@ -53,22 +55,33 @@
 
     public Ed25519PublicKeyParameters generatePublicKey()
     {
-        byte[] publicKey = new byte[Ed25519.PUBLIC_KEY_SIZE];
-        Ed25519.generatePublicKey(data, 0, publicKey, 0);
-        return new Ed25519PublicKeyParameters(publicKey, 0);
+        synchronized (data)
+        {
+            if (null == cachedPublicKey)
+            {
+                byte[] publicKey = new byte[Ed25519.PUBLIC_KEY_SIZE];
+                Ed25519.generatePublicKey(data, 0, publicKey, 0);
+                cachedPublicKey = new Ed25519PublicKeyParameters(publicKey, 0);
+            }
+
+            return cachedPublicKey;
+        }
     }
 
+    /**
+     * @deprecated use overload that doesn't take a public key
+     */
     public void sign(int algorithm, Ed25519PublicKeyParameters publicKey, byte[] ctx, byte[] msg, int msgOff, int msgLen, byte[] sig, int sigOff)
     {
+        sign(algorithm, ctx, msg, msgOff, msgLen, sig, sigOff);
+    }
+
+    public void sign(int algorithm, byte[] ctx, byte[] msg, int msgOff, int msgLen, byte[] sig, int sigOff)
+    {
+        Ed25519PublicKeyParameters publicKey = generatePublicKey();
+
         byte[] pk = new byte[Ed25519.PUBLIC_KEY_SIZE];
-        if (null == publicKey)
-        {
-            Ed25519.generatePublicKey(data, 0, pk, 0);
-        }
-        else
-        {
-            publicKey.encode(pk, 0);
-        }
+        publicKey.encode(pk, 0);
 
         switch (algorithm)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed448PrivateKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed448PrivateKeyParameters.java
index be50146..7e86b84 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed448PrivateKeyParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/Ed448PrivateKeyParameters.java
@@ -17,6 +17,8 @@
 
     private final byte[] data = new byte[KEY_SIZE];
 
+    private Ed448PublicKeyParameters cachedPublicKey;
+
     public Ed448PrivateKeyParameters(SecureRandom random)
     {
         super(true);
@@ -53,22 +55,33 @@
 
     public Ed448PublicKeyParameters generatePublicKey()
     {
-        byte[] publicKey = new byte[Ed448.PUBLIC_KEY_SIZE];
-        Ed448.generatePublicKey(data, 0, publicKey, 0);
-        return new Ed448PublicKeyParameters(publicKey, 0);
+        synchronized (data)
+        {
+            if (null == cachedPublicKey)
+            {
+                byte[] publicKey = new byte[Ed448.PUBLIC_KEY_SIZE];
+                Ed448.generatePublicKey(data, 0, publicKey, 0);
+                cachedPublicKey = new Ed448PublicKeyParameters(publicKey, 0);
+            }
+
+            return cachedPublicKey;
+        }
     }
 
+    /**
+     * @deprecated use overload that doesn't take a public key
+     */
     public void sign(int algorithm, Ed448PublicKeyParameters publicKey, byte[] ctx, byte[] msg, int msgOff, int msgLen, byte[] sig, int sigOff)
     {
+        sign(algorithm, ctx, msg, msgOff, msgLen, sig, sigOff);
+    }
+
+    public void sign(int algorithm, byte[] ctx, byte[] msg, int msgOff, int msgLen, byte[] sig, int sigOff)
+    {
+        Ed448PublicKeyParameters publicKey = generatePublicKey();
+
         byte[] pk = new byte[Ed448.PUBLIC_KEY_SIZE];
-        if (null == publicKey)
-        {
-            Ed448.generatePublicKey(data, 0, pk, 0);
-        }
-        else
-        {
-            publicKey.encode(pk, 0);
-        }
+        publicKey.encode(pk, 0);
 
         switch (algorithm)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java
index 758fcd7..ace103b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyGenerationParameters.java
@@ -8,7 +8,7 @@
  * Parameters for NaccacheStern public private key generation. For details on
  * this cipher, please see
  * 
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  */
 public class NaccacheSternKeyGenerationParameters extends KeyGenerationParameters
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java
index 21b6a28..329bdde 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternKeyParameters.java
@@ -6,7 +6,7 @@
  * Public key parameters for NaccacheStern cipher. For details on this cipher,
  * please see
  * 
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  */
 public class NaccacheSternKeyParameters extends AsymmetricKeyParameter
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java
index 6d0ec48..a2d73fa 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/NaccacheSternPrivateKeyParameters.java
@@ -7,7 +7,7 @@
  * Private key parameters for NaccacheStern cipher. For details on this cipher,
  * please see
  * 
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  */
 public class NaccacheSternPrivateKeyParameters extends NaccacheSternKeyParameters 
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java b/bcprov/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java
index 9c6ed02..9492f5a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/params/ParametersWithRandom.java
@@ -15,14 +15,14 @@
         CipherParameters    parameters,
         SecureRandom        random)
     {
-        this.random = random;
+        this.random = CryptoServicesRegistrar.getSecureRandom(random);
         this.parameters = parameters;
     }
 
     public ParametersWithRandom(
         CipherParameters    parameters)
     {
-        this(parameters, CryptoServicesRegistrar.getSecureRandom());
+        this(parameters, null);
     }
 
     public SecureRandom getRandom()
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java
index c39760c..922342d 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/DRBGProvider.java
@@ -4,5 +4,7 @@
 
 interface DRBGProvider
 {
+    String getAlgorithm();
+
     SP80090DRBG get(EntropySource entropySource);
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java
index 000b0c4..d64ce17 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandom.java
@@ -45,6 +45,11 @@
         }
     }
 
+    public String getAlgorithm()
+    {
+         return drbgProvider.getAlgorithm();
+    }
+
     public void nextBytes(byte[] bytes)
     {
         synchronized (this)
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
index 420cf12..5e5a47b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/SP800SecureRandomBuilder.java
@@ -6,6 +6,8 @@
 import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.engines.DESedeEngine;
+import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.crypto.prng.drbg.CTRSP800DRBG;
 import org.bouncycastle.crypto.prng.drbg.HMacSP800DRBG;
 import org.bouncycastle.crypto.prng.drbg.HashSP800DRBG;
@@ -160,6 +162,11 @@
             this.securityStrength = securityStrength;
         }
 
+        public String getAlgorithm()
+        {
+            return "HASH-DRBG-" + getSimplifiedName(digest);
+        }
+
         public SP80090DRBG get(EntropySource entropySource)
         {
             return new HashSP800DRBG(digest, securityStrength, entropySource, personalizationString, nonce);
@@ -182,6 +189,16 @@
             this.securityStrength = securityStrength;
         }
 
+        public String getAlgorithm()
+        {
+            if (hMac instanceof HMac)
+            {
+                return "HMAC-DRBG-" + getSimplifiedName(((HMac)hMac).getUnderlyingDigest());
+            }
+
+            return "HMAC-DRBG-" + hMac.getAlgorithmName();
+        }
+
         public SP80090DRBG get(EntropySource entropySource)
         {
             return new HMacSP800DRBG(hMac, securityStrength, entropySource, personalizationString, nonce);
@@ -207,9 +224,32 @@
             this.securityStrength = securityStrength;
         }
 
+        public String getAlgorithm()
+        {
+            if (blockCipher instanceof DESedeEngine)
+            {
+                return "CTR-DRBG-3KEY-TDES";
+            }
+            return "CTR-DRBG-" + blockCipher.getAlgorithmName() + keySizeInBits;
+        }
+
         public SP80090DRBG get(EntropySource entropySource)
         {
             return new CTRSP800DRBG(blockCipher, keySizeInBits, securityStrength, entropySource, personalizationString, nonce);
         }
     }
+
+    private static String getSimplifiedName(Digest digest)
+    {
+        String name = digest.getAlgorithmName();
+
+        int dIndex = name.indexOf('-');
+        if (dIndex > 0 && !name.startsWith("SHA3"))
+        {
+            return name.substring(0, dIndex) + name.substring(dIndex + 1);
+        }
+
+        return name;
+    }
+
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java
index 3d37a78..6fc85d3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/CTRDRBGTest.java
@@ -4,7 +4,6 @@
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.DataLengthException;
 import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.engines.AESFastEngine;
 import org.bouncycastle.crypto.engines.DESedeEngine;
 import org.bouncycastle.crypto.params.DESedeParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
@@ -143,7 +142,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBC"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 128,
+                            new AESEngine(), 128,
                             new Bit256EntropyProvider().get(256),
                             false,
                             "2021222324252627",
@@ -155,7 +154,7 @@
                                 }
                         ),
                 new DRBGTestVector(
-                            new AESFastEngine(), 128,
+                            new AESEngine(), 128,
                             new Bit256EntropyProvider().get(256),
                             false,
                             "2021222324252627",
@@ -169,7 +168,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"),
                 new DRBGTestVector(
-                           new AESFastEngine(), 128,
+                           new AESEngine(), 128,
                            new Bit256EntropyProvider().get(256),
                            false,
                            "2021222324252627",
@@ -182,7 +181,7 @@
                        )
                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 128,
+                            new AESEngine(), 128,
                             new Bit256EntropyProvider().get(256),
                             true,
                             "2021222324252627",
@@ -194,7 +193,7 @@
                                 }
                         ),
                 new DRBGTestVector(
-                            new AESFastEngine(), 128,
+                            new AESEngine(), 128,
                             new Bit256EntropyProvider().get(256),
                             true,
                             "2021222324252627",
@@ -208,7 +207,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"),
                 new DRBGTestVector(
-                           new AESFastEngine(), 128,
+                           new AESEngine(), 128,
                            new Bit256EntropyProvider().get(256),
                            true,
                            "2021222324252627",
@@ -221,7 +220,7 @@
                        )
                .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 192,
+                            new AESEngine(), 192,
                             new Bit320EntropyProvider().get(320),
                             false,
                             "202122232425262728292A2B",
@@ -234,7 +233,7 @@
                         )
                 .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F6061626364656667"),
                 new DRBGTestVector(
-                        new AESFastEngine(), 192,
+                        new AESEngine(), 192,
                         new Bit320EntropyProvider().get(320),
                         true,
                         "202122232425262728292A2B",
@@ -247,7 +246,7 @@
                     )
             .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F6061626364656667"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 256,
+                            new AESEngine(), 256,
                             new Bit384EntropyProvider().get(384),
                             false,
                             "202122232425262728292A2B2C2D2E2F",
@@ -262,7 +261,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 256,
+                            new AESEngine(), 256,
                             new Bit384EntropyProvider().get(384),
                             true,
                             "202122232425262728292A2B2C2D2E2F",
@@ -276,7 +275,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 256,
+                            new AESEngine(), 256,
                             new Bit384EntropyProvider().get(384),
                             true,
                             "202122232425262728292A2B2C2D2E2F",
@@ -289,7 +288,7 @@
                         )
                 .setPersonalizationString("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 256,
+                            new AESEngine(), 256,
                             new Bit384EntropyProvider().get(384),
                             true,
                             "202122232425262728292A2B2C2D2E2F",
@@ -304,7 +303,7 @@
                 .addAdditionalInput("606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F")
                 .addAdditionalInput("A0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"),
                 new DRBGTestVector(
-                            new AESFastEngine(), 256,
+                            new AESEngine(), 256,
                             new Bit384EntropyProvider().get(384),
                             true,
                             "202122232425262728292A2B2C2D2E2F",
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DRBGTestVector.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DRBGTestVector.java
index dd6801c..1107f71 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DRBGTestVector.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DRBGTestVector.java
@@ -10,122 +10,155 @@
 
 public class DRBGTestVector
 {
-        private Digest _digest;
-        private BlockCipher _cipher;
-        private int _keySizeInBits;
-        private EntropySource _eSource;
-        private boolean _pr;
-        private String _nonce;
-        private String _personalisation;
-        private int _ss;
-        private String[] _ev;
-        private List _ai = new ArrayList();
+    private String _name;
+    private Digest _digest;
+    private BlockCipher _cipher;
+    private int _keySizeInBits;
+    private EntropySource _eSource;
+    private boolean _pr;
+    private String _nonce;
+    private String _personalisation;
+    private int _ss;
+    private String[] _ev;
+    private List _ai = new ArrayList();
 
-        public DRBGTestVector(Digest digest, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
-        {
-            _digest = digest;
-            _eSource = eSource;
-            _pr = predictionResistance;
-            _nonce = nonce;
-            _ss = securityStrength;
-            _ev = expected;
-            _personalisation = null;
-        }
-
-        public DRBGTestVector(BlockCipher cipher, int keySizeInBits, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
-        {
-            _cipher = cipher;
-            _keySizeInBits = keySizeInBits;
-            _eSource = eSource;
-            _pr = predictionResistance;
-            _nonce = nonce;
-            _ss = securityStrength;
-            _ev = expected;
-            _personalisation = null;
-        }
-
-        public Digest getDigest()
-        {
-            return _digest;
-        }
-
-        public BlockCipher getCipher()
-        {
-            return _cipher;
-        }
-
-        public int keySizeInBits()
-        {
-            return _keySizeInBits;
-        }
-
-        public DRBGTestVector addAdditionalInput(String input)
-        {
-            _ai.add(input);
-
-            return this;
-        }
-
-        public DRBGTestVector setPersonalizationString(String p)
-        {
-            _personalisation = p;
-
-            return this;
-        }
-
-        public EntropySource entropySource()
-        {
-            return _eSource;
-        }
-
-        public boolean predictionResistance()
-        {
-            return _pr;
-        }
-
-        public byte[] nonce()
-        {
-            if (_nonce == null)
-            {
-                return null;
-            }
-
-            return Hex.decode(_nonce);
-        }
-
-        public byte[] personalizationString()
-        {
-            if (_personalisation == null)
-            {
-                return null;
-            }
-
-            return Hex.decode(_personalisation);
-        }
-
-        public int securityStrength()
-        {
-            return _ss;
-        }
-
-        public byte[] expectedValue(int index)
-        {
-            return Hex.decode(_ev[index]);
-        }
-
-        public byte[] additionalInput(int position)
-        {
-            int len = _ai.size();
-            byte[] rv;
-            if (position >= len)
-            {
-                rv = null;
-            }
-            else
-            {
-                rv = Hex.decode((String)(_ai.get(position)));
-            }
-            return rv;
-        }
-
+    public DRBGTestVector(Digest digest, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String name, String[] expected)
+    {
+        _digest = digest;
+        _eSource = eSource;
+        _pr = predictionResistance;
+        _nonce = nonce;
+        _ss = securityStrength;
+        _ev = expected;
+        _name = name;
+        _personalisation = null;
     }
+
+    public DRBGTestVector(Digest digest, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
+    {
+        _digest = digest;
+        _eSource = eSource;
+        _pr = predictionResistance;
+        _nonce = nonce;
+        _ss = securityStrength;
+        _ev = expected;
+        _name = null;
+        _personalisation = null;
+    }
+
+    public DRBGTestVector(BlockCipher cipher, int keySizeInBits, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String name, String[] expected)
+    {
+        _cipher = cipher;
+        _keySizeInBits = keySizeInBits;
+        _eSource = eSource;
+        _pr = predictionResistance;
+        _nonce = nonce;
+        _ss = securityStrength;
+        _ev = expected;
+        _name = name;
+        _personalisation = null;
+    }
+
+    public DRBGTestVector(BlockCipher cipher, int keySizeInBits, EntropySource eSource, boolean predictionResistance, String nonce, int securityStrength, String[] expected)
+    {
+        _cipher = cipher;
+        _keySizeInBits = keySizeInBits;
+        _eSource = eSource;
+        _pr = predictionResistance;
+        _nonce = nonce;
+        _ss = securityStrength;
+        _ev = expected;
+        _name = null;
+        _personalisation = null;
+    }
+
+    public Digest getDigest()
+    {
+        return _digest;
+    }
+
+    public BlockCipher getCipher()
+    {
+        return _cipher;
+    }
+
+    public int keySizeInBits()
+    {
+        return _keySizeInBits;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public DRBGTestVector addAdditionalInput(String input)
+    {
+        _ai.add(input);
+
+        return this;
+    }
+
+    public DRBGTestVector setPersonalizationString(String p)
+    {
+        _personalisation = p;
+
+        return this;
+    }
+
+    public EntropySource entropySource()
+    {
+        return _eSource;
+    }
+
+    public boolean predictionResistance()
+    {
+        return _pr;
+    }
+
+    public byte[] nonce()
+    {
+        if (_nonce == null)
+        {
+            return null;
+        }
+
+        return Hex.decode(_nonce);
+    }
+
+    public byte[] personalizationString()
+    {
+        if (_personalisation == null)
+        {
+            return null;
+        }
+
+        return Hex.decode(_personalisation);
+    }
+
+    public int securityStrength()
+    {
+        return _ss;
+    }
+
+    public byte[] expectedValue(int index)
+    {
+        return Hex.decode(_ev[index]);
+    }
+
+    public byte[] additionalInput(int position)
+    {
+        int len = _ai.size();
+        byte[] rv;
+        if (position >= len)
+        {
+            rv = null;
+        }
+        else
+        {
+            rv = Hex.decode((String)(_ai.get(position)));
+        }
+        return rv;
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java
index d6c2630..6354966 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/DualECDRBGTest.java
@@ -258,7 +258,7 @@
                             "815B6D34E29F2603526CE186576F4CCA3FEDF7F8ACDB37C9" +
                             "9D762706ABE4967D44739C8CFCFCC76C58B1ED243AC394C0"
                         }),
-                // From http://csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors.zip
+                // From https://csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors.zip
                 // modified to test partial block processing.
                 new DRBGTestVector(
                     new SHA256Digest(),
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/RegressionTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/RegressionTest.java
index 9b03637..8b65868 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/RegressionTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/RegressionTest.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.crypto.prng.test;
 
+import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
 
 public class RegressionTest
 {
@@ -15,20 +15,8 @@
         new FixedSecureRandomTest()
     };
 
-    public static void main(
-        String[]    args)
+    public static void main(String[] args)
     {
-        for (int i = 0; i != tests.length; i++)
-        {
-            TestResult  result = tests[i].perform();
-            
-            if (result.getException() != null)
-            {
-                result.getException().printStackTrace();
-            }
-            
-            System.out.println(result);
-        }
+        SimpleTest.runTests(tests);
     }
 }
-
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/SP800RandomTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/SP800RandomTest.java
index 3dfe115..77bb79a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/SP800RandomTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/prng/test/SP800RandomTest.java
@@ -4,6 +4,9 @@
 
 import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA384Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.engines.AESEngine;
 import org.bouncycastle.crypto.engines.DESedeEngine;
 import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.crypto.prng.BasicEntropySourceProvider;
@@ -29,6 +32,7 @@
                             true,
                             "2021222324",
                             80,
+                            "HASH-DRBG-SHA1",
                             new String[]
                                 {
                                     "532CA1165DCFF21C55592687639884AF4BC4B057DF8F41DE653AB44E2ADEC7C9303E75ABE277EDBF",
@@ -44,6 +48,7 @@
                             false,
                             "2021222324",
                             80,
+                            "HASH-DRBG-SHA1",
                             new String[]
                                 {
                                     "AB438BD3B01A0AF85CFEE29F7D7B71621C4908B909124D430E7B406FB1086EA994C582E0D656D989",
@@ -81,6 +86,8 @@
         {
             fail(index + " SP800 Hash SecureRandom produced incorrect result (2)");
         }
+
+        isTrue(random.getAlgorithm(), tv.getName().equals(random.getAlgorithm()));
     }
 
     private void testHMACRandom()
@@ -91,6 +98,7 @@
             true,
             "2021222324",
             80,
+            "HMAC-DRBG-SHA1",
             new String[]
                 {
                     "6C37FDD729AA40F80BC6AB08CA7CC649794F6998B57081E4220F22C5C283E2C91B8E305AB869C625",
@@ -106,6 +114,7 @@
                 false,
                 "2021222324",
                 80,
+                "HMAC-DRBG-SHA1",
                 new String[]
                     {
                         "5A7D3B449F481CB38DF79AD2B1FCC01E57F8135E8C0B22CD0630BFB0127FB5408C8EFC17A929896E",
@@ -141,6 +150,8 @@
         {
             fail("SP800 HMAC SecureRandom produced incorrect result (2)");
         }
+
+        isTrue(random.getAlgorithm(), tv.getName().equals(random.getAlgorithm()));
     }
 
     private void testCTRRandom()
@@ -151,6 +162,7 @@
                                     false,
                                     "20212223242526",
                                     112,
+                                    "CTR-DRBG-3KEY-TDES",
                                     new String[]
                                         {
                                             "ABC88224514D0316EA3D48AEE3C9A2B4",
@@ -166,6 +178,7 @@
                     true,
                     "20212223242526",
                     112,
+                    "CTR-DRBG-3KEY-TDES",
                     new String[]
                         {
                             "64983055D014550B39DE699E43130B64",
@@ -203,6 +216,8 @@
         {
             fail("SP800 CTR SecureRandom produced incorrect result (2)");
         }
+
+        isTrue(random.getAlgorithm(), tv.getName().equals(random.getAlgorithm()));
     }
 
     private void testGenerateSeed()
@@ -233,6 +248,29 @@
         }
     }
 
+    private void testNames()
+    {
+        SP800SecureRandomBuilder rBuild = new SP800SecureRandomBuilder(new Bit232EntropyProvider());
+
+        rBuild.setPersonalizationString(Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C"));
+        rBuild.setSecurityStrength(112);
+        rBuild.setEntropyBitsRequired(232);
+
+        isEquals("CTR-DRBG-3KEY-TDES", rBuild.buildCTR(new DESedeEngine(), 168, Hex.decode("20212223242526"), false).getAlgorithm());
+
+        isEquals("CTR-DRBG-AES128", rBuild.buildCTR(new AESEngine(), 128, Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("CTR-DRBG-AES192", rBuild.buildCTR(new AESEngine(), 192, Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("CTR-DRBG-AES256", rBuild.buildCTR(new AESEngine(), 256, Hex.decode("20212223242526"), false).getAlgorithm());
+
+        isEquals("HASH-DRBG-SHA256", rBuild.buildHash(new SHA256Digest(), Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("HASH-DRBG-SHA384", rBuild.buildHash(new SHA384Digest(), Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("HASH-DRBG-SHA512", rBuild.buildHash(new SHA512Digest(), Hex.decode("20212223242526"), false).getAlgorithm());
+
+        isEquals("HMAC-DRBG-SHA256", rBuild.buildHMAC(new HMac(new SHA256Digest()), Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("HMAC-DRBG-SHA384", rBuild.buildHMAC(new HMac(new SHA384Digest()), Hex.decode("20212223242526"), false).getAlgorithm());
+        isEquals("HMAC-DRBG-SHA512", rBuild.buildHMAC(new HMac(new SHA512Digest()), Hex.decode("20212223242526"), false).getAlgorithm());
+    }
+
     public void performTest()
         throws Exception
     {
@@ -240,6 +278,7 @@
         testHMACRandom();
         testCTRRandom();
         testGenerateSeed();
+        testNames();
     }
 
     public static void main(String[] args)
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java
index 668ac27..314a096 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/DSASigner.java
@@ -105,7 +105,7 @@
         // the randomizer is to conceal timing information related to k and x.
         BigInteger  r = params.getG().modPow(k.add(getRandomizer(q, random)), params.getP()).mod(q);
 
-        k = k.modInverse(q).multiply(m.add(x.multiply(r)));
+        k = BigIntegers.modOddInverse(q, k).multiply(m.add(x.multiply(r)));
 
         BigInteger  s = k.mod(q);
 
@@ -137,7 +137,7 @@
             return false;
         }
 
-        BigInteger  w = s.modInverse(q);
+        BigInteger w = BigIntegers.modOddInverseVar(q, s);
 
         BigInteger  u1 = m.multiply(w).mod(q);
         BigInteger  u2 = r.multiply(w).mod(q);
@@ -169,7 +169,7 @@
 
     protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
     {
-        return !needed ? null : (provided != null) ? provided : CryptoServicesRegistrar.getSecureRandom();
+        return needed ? CryptoServicesRegistrar.getSecureRandom(provided) : null;
     }
 
     private BigInteger getRandomizer(BigInteger q, SecureRandom provided)
@@ -177,6 +177,6 @@
         // Calculate a random multiple of q to add to k. Note that g^q = 1 (mod p), so adding multiple of q to k does not change r.
         int randomBits = 7;
 
-        return BigIntegers.createRandomBigInteger(randomBits, provided != null ? provided : CryptoServicesRegistrar.getSecureRandom()).add(BigInteger.valueOf(128)).multiply(q);
+        return BigIntegers.createRandomBigInteger(randomBits, CryptoServicesRegistrar.getSecureRandom(provided)).add(BigInteger.valueOf(128)).multiply(q);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java
index 057beed..c8f687b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECDSASigner.java
@@ -18,6 +18,7 @@
 import org.bouncycastle.math.ec.ECMultiplier;
 import org.bouncycastle.math.ec.ECPoint;
 import org.bouncycastle.math.ec.FixedPointCombMultiplier;
+import org.bouncycastle.util.BigIntegers;
 
 /**
  * EC-DSA as described in X9.62
@@ -125,7 +126,7 @@
             }
             while (r.equals(ZERO));
 
-            s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);
+            s = BigIntegers.modOddInverse(n, k).multiply(e.add(d.multiply(r))).mod(n);
         }
         while (s.equals(ZERO));
 
@@ -159,7 +160,7 @@
             return false;
         }
 
-        BigInteger c = s.modInverse(n);
+        BigInteger c = BigIntegers.modOddInverseVar(n, s);
 
         BigInteger u1 = e.multiply(c).mod(n);
         BigInteger u2 = r.multiply(c).mod(n);
@@ -253,6 +254,6 @@
 
     protected SecureRandom initSecureRandom(boolean needed, SecureRandom provided)
     {
-        return !needed ? null : (provided != null) ? provided : CryptoServicesRegistrar.getSecureRandom();
+        return needed ? CryptoServicesRegistrar.getSecureRandom(provided) : null;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
index 0de497b..447e9d6 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410Signer.java
@@ -130,7 +130,7 @@
             return false;
         }
 
-        BigInteger v = e.modInverse(n);
+        BigInteger v = BigIntegers.modOddInverseVar(n, e);
 
         BigInteger z1 = s.multiply(v).mod(n);
         BigInteger z2 = (n.subtract(r)).multiply(v).mod(n);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410_2012Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410_2012Signer.java
index 9557bc0..7d62d5e 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410_2012Signer.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/ECGOST3410_2012Signer.java
@@ -130,7 +130,7 @@
             return false;
         }
 
-        BigInteger v = e.modInverse(n);
+        BigInteger v = BigIntegers.modOddInverseVar(n, e);
 
         BigInteger z1 = s.multiply(v).mod(n);
         BigInteger z2 = (n.subtract(r)).multiply(v).mod(n);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519Signer.java
index 72d0608..9de19e6 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519Signer.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519Signer.java
@@ -28,10 +28,8 @@
 
         if (forSigning)
         {
-            // TODO Allow AsymmetricCipherKeyPair to be a CipherParameters?
-
             this.privateKey = (Ed25519PrivateKeyParameters)parameters;
-            this.publicKey = privateKey.generatePublicKey();
+            this.publicKey = null;
         }
         else
         {
@@ -59,7 +57,7 @@
             throw new IllegalStateException("Ed25519Signer not initialised for signature generation.");
         }
 
-        return buffer.generateSignature(privateKey, publicKey);
+        return buffer.generateSignature(privateKey);
     }
 
     public boolean verifySignature(byte[] signature)
@@ -79,10 +77,10 @@
 
     private static class Buffer extends ByteArrayOutputStream
     {
-        synchronized byte[] generateSignature(Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey)
+        synchronized byte[] generateSignature(Ed25519PrivateKeyParameters privateKey)
         {
             byte[] signature = new byte[Ed25519PrivateKeyParameters.SIGNATURE_SIZE];
-            privateKey.sign(Ed25519.Algorithm.Ed25519, publicKey, null, buf, 0, count, signature, 0);
+            privateKey.sign(Ed25519.Algorithm.Ed25519, null, buf, 0, count, signature, 0);
             reset();
             return signature;
         }
@@ -91,6 +89,7 @@
         {
             if (Ed25519.SIGNATURE_SIZE != signature.length)
             {
+                reset();
                 return false;
             }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519ctxSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519ctxSigner.java
index 4d88fc8..1b24372 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519ctxSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519ctxSigner.java
@@ -30,10 +30,8 @@
 
         if (forSigning)
         {
-            // TODO Allow AsymmetricCipherKeyPair to be a CipherParameters?
-
             this.privateKey = (Ed25519PrivateKeyParameters)parameters;
-            this.publicKey = privateKey.generatePublicKey();
+            this.publicKey = null;
         }
         else
         {
@@ -61,7 +59,7 @@
             throw new IllegalStateException("Ed25519ctxSigner not initialised for signature generation.");
         }
 
-        return buffer.generateSignature(privateKey, publicKey, context);
+        return buffer.generateSignature(privateKey, context);
     }
 
     public boolean verifySignature(byte[] signature)
@@ -81,10 +79,10 @@
 
     private static class Buffer extends ByteArrayOutputStream
     {
-        synchronized byte[] generateSignature(Ed25519PrivateKeyParameters privateKey, Ed25519PublicKeyParameters publicKey, byte[] ctx)
+        synchronized byte[] generateSignature(Ed25519PrivateKeyParameters privateKey, byte[] ctx)
         {
             byte[] signature = new byte[Ed25519PrivateKeyParameters.SIGNATURE_SIZE];
-            privateKey.sign(Ed25519.Algorithm.Ed25519ctx, publicKey, ctx, buf, 0, count, signature, 0);
+            privateKey.sign(Ed25519.Algorithm.Ed25519ctx, ctx, buf, 0, count, signature, 0);
             reset();
             return signature;
         }
@@ -93,6 +91,7 @@
         {
             if (Ed25519.SIGNATURE_SIZE != signature.length)
             {
+                reset();
                 return false;
             }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519phSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519phSigner.java
index acb074c..7f14db9 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519phSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed25519phSigner.java
@@ -29,10 +29,8 @@
 
         if (forSigning)
         {
-            // TODO Allow AsymmetricCipherKeyPair to be a CipherParameters?
-
             this.privateKey = (Ed25519PrivateKeyParameters)parameters;
-            this.publicKey = privateKey.generatePublicKey();
+            this.publicKey = null;
         }
         else
         {
@@ -67,7 +65,7 @@
         }
 
         byte[] signature = new byte[Ed25519PrivateKeyParameters.SIGNATURE_SIZE];
-        privateKey.sign(Ed25519.Algorithm.Ed25519ph, publicKey, context, msg, 0, Ed25519.PREHASH_SIZE, signature, 0);
+        privateKey.sign(Ed25519.Algorithm.Ed25519ph, context, msg, 0, Ed25519.PREHASH_SIZE, signature, 0);
         return signature;
     }
 
@@ -79,6 +77,7 @@
         }
         if (Ed25519.SIGNATURE_SIZE != signature.length)
         {
+            prehash.reset();
             return false;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448Signer.java
index 00dcdd3..b6233bf 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448Signer.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448Signer.java
@@ -31,10 +31,8 @@
 
         if (forSigning)
         {
-            // TODO Allow AsymmetricCipherKeyPair to be a CipherParameters?
-
             this.privateKey = (Ed448PrivateKeyParameters)parameters;
-            this.publicKey = privateKey.generatePublicKey();
+            this.publicKey = null;
         }
         else
         {
@@ -62,7 +60,7 @@
             throw new IllegalStateException("Ed448Signer not initialised for signature generation.");
         }
 
-        return buffer.generateSignature(privateKey, publicKey, context);
+        return buffer.generateSignature(privateKey, context);
     }
 
     public boolean verifySignature(byte[] signature)
@@ -82,10 +80,10 @@
 
     private static class Buffer extends ByteArrayOutputStream
     {
-        synchronized byte[] generateSignature(Ed448PrivateKeyParameters privateKey, Ed448PublicKeyParameters publicKey, byte[] ctx)
+        synchronized byte[] generateSignature(Ed448PrivateKeyParameters privateKey, byte[] ctx)
         {
             byte[] signature = new byte[Ed448PrivateKeyParameters.SIGNATURE_SIZE];
-            privateKey.sign(Ed448.Algorithm.Ed448, publicKey, ctx, buf, 0, count, signature, 0);
+            privateKey.sign(Ed448.Algorithm.Ed448, ctx, buf, 0, count, signature, 0);
             reset();
             return signature;
         }
@@ -94,6 +92,7 @@
         {
             if (Ed448.SIGNATURE_SIZE != signature.length)
             {
+                reset();
                 return false;
             }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448phSigner.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448phSigner.java
index e236823..a701f72 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448phSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/Ed448phSigner.java
@@ -29,10 +29,8 @@
 
         if (forSigning)
         {
-            // TODO Allow AsymmetricCipherKeyPair to be a CipherParameters?
-
             this.privateKey = (Ed448PrivateKeyParameters)parameters;
-            this.publicKey = privateKey.generatePublicKey();
+            this.publicKey = null;
         }
         else
         {
@@ -67,7 +65,7 @@
         }
 
         byte[] signature = new byte[Ed448PrivateKeyParameters.SIGNATURE_SIZE];
-        privateKey.sign(Ed448.Algorithm.Ed448ph, publicKey, context, msg, 0, Ed448.PREHASH_SIZE, signature, 0);
+        privateKey.sign(Ed448.Algorithm.Ed448ph, context, msg, 0, Ed448.PREHASH_SIZE, signature, 0);
         return signature;
     }
 
@@ -79,6 +77,7 @@
         }
         if (Ed448.SIGNATURE_SIZE != signature.length)
         {
+            prehash.reset();
             return false;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/signers/SM2Signer.java b/bcprov/src/main/java/org/bouncycastle/crypto/signers/SM2Signer.java
index 7de1286..fbb8ae0 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/signers/SM2Signer.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/signers/SM2Signer.java
@@ -20,6 +20,7 @@
 import org.bouncycastle.math.ec.ECMultiplier;
 import org.bouncycastle.math.ec.ECPoint;
 import org.bouncycastle.math.ec.FixedPointCombMultiplier;
+import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.encoders.Hex;
 
 /**
@@ -177,7 +178,7 @@
             while (r.equals(ZERO) || r.add(k).equals(n));
 
             // A6
-            BigInteger dPlus1ModN = d.add(ONE).modInverse(n);
+            BigInteger dPlus1ModN = BigIntegers.modOddInverse(n, d.add(ONE));
 
             s = k.subtract(r.multiply(d)).mod(n);
             s = dPlus1ModN.multiply(s).mod(n);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/AEADTestUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/AEADTestUtil.java
index 5ffa72a..d951795 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/AEADTestUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/AEADTestUtil.java
@@ -5,6 +5,7 @@
 import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.OutputLengthException;
 import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.AEADCipher;
 import org.bouncycastle.crypto.params.AEADParameters;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -14,7 +15,7 @@
 
 public class AEADTestUtil
 {
-    public static void testTampering(Test test, AEADBlockCipher cipher, CipherParameters params)
+    public static void testTampering(Test test, AEADCipher cipher, CipherParameters params)
         throws InvalidCipherTextException
     {
         byte[] plaintext = new byte[1000];
@@ -76,7 +77,7 @@
         throw new TestFailedException(SimpleTestResult.failed(test, message, expected, result));
     }
 
-    public static void testReset(Test test, AEADBlockCipher cipher1, AEADBlockCipher cipher2, CipherParameters params)
+    public static void testReset(Test test, AEADCipher cipher1, AEADBlockCipher cipher2, CipherParameters params)
         throws InvalidCipherTextException
     {
         cipher1.init(true, params);
@@ -96,7 +97,7 @@
     }
 
     private static void checkReset(Test test,
-                                   AEADBlockCipher cipher,
+                                   AEADCipher cipher,
                                    CipherParameters params,
                                    boolean encrypt,
                                    byte[] pretext,
@@ -183,7 +184,7 @@
         }
     }
 
-    private static void crypt(AEADBlockCipher cipher, byte[] plaintext, byte[] output)
+    private static void crypt(AEADCipher cipher, byte[] plaintext, byte[] output)
         throws InvalidCipherTextException
     {
         int len = cipher.processBytes(plaintext, 0, plaintext.length, output, 0);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/AESWrapPadTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/AESWrapPadTest.java
index 93342cb..bd4c1ab 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/AESWrapPadTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/AESWrapPadTest.java
@@ -2,6 +2,7 @@
 
 import java.security.SecureRandom;
 
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.Wrapper;
 import org.bouncycastle.crypto.engines.AESWrapPadEngine;
 import org.bouncycastle.crypto.params.KeyParameter;
@@ -162,6 +163,58 @@
             rnd.nextBytes(keyToWrap);
             wrapAndUnwrap(kek, keyToWrap);
         }
+
+        performFailTests();
+    }
+
+    private void performFailTests() {
+        // Tests of specific failure modes. Each failure mode should produce an InvalidCipherTextException
+        byte[] kek = Hex.decode("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8");
+        Wrapper wrapper = new AESWrapPadEngine();
+        wrapper.init(false, new KeyParameter(kek));
+
+        try {
+            // not a multiple of 8 bytes, so invalid
+            wrapper.unwrap(new byte[23],0,23);
+            fail("Data which was not a multiple of 8 bytes was accepted");
+        } catch ( InvalidCipherTextException e ) {
+            // correct behaviour
+        }
+
+        try {
+            // zero bytes is too short
+            wrapper.unwrap(new byte[0],0,0);
+            fail("Data of zero bytes was accepted");
+        } catch ( InvalidCipherTextException e ) {
+            // correct behaviour
+        }
+
+        try {
+            // only 8 bytes - so too short
+            wrapper.unwrap(new byte[8],0,8);
+            fail("Data which was not a multiple of 8 bytes was accepted");
+        } catch ( InvalidCipherTextException e ) {
+            // correct behaviour
+        }
+
+        try {
+            // usable number of bytes, but cannot be decrypted.
+            // This value produces a negative MLI.
+            wrapper.unwrap(Hex.decode("000000000000000000000000000000000000000000000000"),0,24);
+            fail("Invalid data was accepted");
+        } catch ( InvalidCipherTextException e ) {
+            // correct behaviour
+        }
+
+
+        try {
+            // usable number of bytes, but cannot be decrypted.
+            // This value produces a large positive MLI
+            wrapper.unwrap(Hex.decode("000000000000000000000000000000000000000000000002"),0,24);
+            fail("Invalid data was accepted");
+        } catch ( InvalidCipherTextException e ) {
+            // correct behaviour
+        }
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/AllTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/AllTests.java
index 0478488..4374087 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/AllTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/AllTests.java
@@ -4,7 +4,6 @@
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
-import org.bouncycastle.util.test.SimpleTestResult;
 
 public class AllTests
     extends TestCase
@@ -23,30 +22,7 @@
         
         return new BCTestSetup(suite);
     }
-
-    public static class SimpleTestTest
-       extends TestCase
-    {
-        public void testCrypto()
-        {
-            org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
-
-            for (int i = 0; i != tests.length; i++)
-            {
-                SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
-
-                if (!result.isSuccessful())
-                {
-                    if (result.getException() != null)
-                    {
-                        result.getException().printStackTrace();
-                    }
-                    fail(result.toString());
-                }
-            }
-        }
-    }
-
+    
     static class BCTestSetup
         extends TestSetup
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Argon2Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Argon2Test.java
index 6fa1eab..0e32813 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/Argon2Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Argon2Test.java
@@ -1,15 +1,17 @@
 package org.bouncycastle.crypto.test;
 
 
+import java.util.ArrayList;
+
 import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
 import org.bouncycastle.crypto.params.Argon2Parameters;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
  * Tests from https://tools.ietf.org/html/draft-irtf-cfrg-argon2-03
- *
  */
 public class Argon2Test
     extends SimpleTest
@@ -29,11 +31,13 @@
             return;
         }
 
+        testPermutations();
         testVectorsFromInternetDraft();
 
         int version = Argon2Parameters.ARGON2_VERSION_10;
 
 
+
         /* Multiple test cases for various input values */
         hashTest(version, 2, 16, 1, "password", "somesalt",
             "f6c4db4a54e2a370627aff3db6176b94a2a209a62c8e36152711802f7b30c694", DEFAULT_OUTPUTLEN);
@@ -98,6 +102,120 @@
     }
 
 
+    public void testPermutations()
+        throws Exception
+    {
+
+        byte[] rootPassword = Strings.toByteArray("aac");
+        byte[] buf = null;
+
+        byte[][] salts = new byte[3][];
+
+        salts[0] = new byte[16];
+        salts[1] = new byte[16];
+        salts[2] = new byte[16];
+        for (int t = 0; t < 16; t++)
+        {
+            salts[1][t] = (byte)t;
+            salts[2][t] = (byte)(16 - t);
+        }
+
+
+        //
+        // Permutation, starting with a shorter array, same length then one longer.
+        //
+        for (int j = rootPassword.length - 1; j < rootPassword.length + 2; j++)
+        {
+            buf = new byte[j];
+
+            for (int a = 0; a < rootPassword.length; a++)
+            {
+                for (int b = 0; b < buf.length; b++)
+                {
+                    buf[b] = rootPassword[(a + b) % rootPassword.length];
+                }
+
+
+                ArrayList<byte[]> permutations = new ArrayList<byte[]>();
+                permute(permutations, buf, 0, buf.length - 1);
+
+                for (int i = 0; i != permutations.size(); i++)
+                {
+                    byte[] candidate = (byte[])permutations.get(i);
+                    for (int k = 0; k != salts.length; k++)
+                    {
+                        byte[] salt = salts[k];
+                        byte[] expected = generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, rootPassword, salt, 32);
+                        byte[] testValue = generate(Argon2Parameters.ARGON2_VERSION_10, 1, 8, 2, candidate, salt, 32);
+
+                        //
+                        // If the passwords are the same for the same salt we should have the same string.
+                        //
+                        boolean sameAsRoot = Arrays.areEqual(rootPassword, candidate);
+                        isTrue("expected same result", sameAsRoot == Arrays.areEqual(expected, testValue));
+
+                    }
+
+                }
+            }
+        }
+    }
+
+    private void swap(byte[] buf, int i, int j)
+    {
+        byte b = buf[i];
+        buf[i] = buf[j];
+        buf[j] = b;
+    }
+
+    private void permute(ArrayList<byte[]> permutation, byte[] a, int l, int r)
+    {
+        if (l == r)
+        {
+            permutation.add(Arrays.clone(a));
+        }
+        else
+        {
+
+            for (int i = l; i <= r; i++)
+            {
+                // Swapping done
+                swap(a, l, i);
+
+                // Recursion called
+                permute(permutation, a, l + 1, r);
+
+                //backtrack
+                swap(a, l, i);
+            }
+        }
+    }
+
+
+    private byte[] generate(int version, int iterations, int memory, int parallelism,
+                            byte[] password, byte[] salt, int outputLength)
+    {
+        Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_i)
+            .withVersion(version)
+            .withIterations(iterations)
+            .withMemoryPowOfTwo(memory)
+            .withParallelism(parallelism)
+            .withSalt(salt);
+
+        //
+        // Set the password.
+        //
+        Argon2BytesGenerator gen = new Argon2BytesGenerator();
+
+        gen.init(builder.build());
+
+        byte[] result = new byte[outputLength];
+
+        gen.generateBytes(password, result, 0, result.length);
+        return result;
+    }
+
+
     private void hashTest(int version, int iterations, int memory, int parallelism,
                           String password, String salt, String passwordRef, int outputLength)
     {
@@ -116,9 +234,14 @@
         gen.init(builder.build());
 
         byte[] result = new byte[outputLength];
+
         gen.generateBytes(password.toCharArray(), result, 0, result.length);
+        isTrue(passwordRef + " Failed", areEqual(result, Hex.decode(passwordRef)));
 
+        Arrays.clear(result);
 
+        // Should be able to re-use generator after successful use
+        gen.generateBytes(password.toCharArray(), result, 0, result.length);
         isTrue(passwordRef + " Failed", areEqual(result, Hex.decode(passwordRef)));
     }
 
@@ -129,7 +252,6 @@
      * @throws Exception
      */
     private void testVectorsFromInternetDraft()
-        throws Exception
     {
         byte[] ad = Hex.decode("040404040404040404040404");
         byte[] secret = Hex.decode("0303030303030303");
@@ -182,7 +304,7 @@
             .withAdditional(ad)
             .withSecret(secret)
             .withSalt(salt);
-        
+
         dig = new Argon2BytesGenerator();
 
         dig.init(builder.build());
@@ -196,30 +318,32 @@
 
     private static int getJvmVersion()
     {
-        String version = System.getProperty("java.version");
-
-        if (version.startsWith("1.7"))
+        String version = System.getProperty("java.specification.version");
+        if (null == version)
         {
-            return 7;
+            return -1;
         }
-        if (version.startsWith("1.8"))
+        String[] parts = version.split("\\.");
+        if (parts == null || parts.length < 1)
         {
-            return 8;
+            return -1;
         }
-        if (version.startsWith("1.9"))
+        try
         {
-            return 9;
+            int major = Integer.parseInt(parts[0]);
+            if (major == 1 && parts.length > 1)
+            {
+                return Integer.parseInt(parts[1]);
+            }
+            return major;
         }
-        if (version.startsWith("1.1"))
+        catch (NumberFormatException e)
         {
-            return 10;
+            return -1;
         }
-
-        return -1;
     }
 
     public static void main(String[] args)
-        throws Exception
     {
         runTest(new Argon2Test());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/BigIntegersTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/BigIntegersTest.java
new file mode 100644
index 0000000..c84efbb
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/BigIntegersTest.java
@@ -0,0 +1,38 @@
+package org.bouncycastle.crypto.test;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.TestRandomData;
+
+public class BigIntegersTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "BigIntegers";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        BigInteger min = BigInteger.valueOf(5);
+        isTrue(min.equals(BigIntegers.createRandomPrime(min.bitLength(), 1,
+            new TestRandomData(BigIntegers.asUnsignedByteArray(min)))));
+
+        BigInteger max = BigInteger.valueOf(743);
+        isTrue(max.equals(BigIntegers.createRandomPrime(max.bitLength(), 1,
+            new TestRandomData(BigIntegers.asUnsignedByteArray(max)))));
+
+        isTrue(1 == BigIntegers.asUnsignedByteArray(BigIntegers.ZERO).length);
+        isTrue(1 == BigIntegers.getUnsignedByteLength(BigIntegers.ZERO));
+        isTrue(1 == BigIntegers.getUnsignedByteLength(BigIntegers.ONE));
+    }
+
+    public static void main(String[] args)
+        throws Exception
+    {
+        runTest(new BigIntegersTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2xsDigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2xsDigestTest.java
new file mode 100644
index 0000000..3f2f489
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Blake2xsDigestTest.java
@@ -0,0 +1,2690 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.digests.Blake2xsDigest;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class Blake2xsDigestTest
+    extends SimpleTest
+{
+
+    // https://github.com/BLAKE2/BLAKE2/blob/master/testvectors/blake2-kat.json
+    private static final String[][] xofTestVectors = {
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "99"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "57d5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "72d07f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "bdf28396"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "20e81fc0f3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "53d87da652c6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "dea6abdba2b385"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e8ef785d84bed985"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8564786ae17558a034"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8fe7cf0bedfc5c8a25c4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ced64dbdb850b8d9238544"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a61e711081c80de67b0f5cd3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ca84913682c32af70a5a762e96"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b3051e87aeb0e2f29d4197ea1001"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f1db5e2f2bde30d08125a67d718b3a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "541e57a4988909ea2f81953f6ca1cb75"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "58910f890077e12ec101610597195ddc0e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "42a2b79173ee4f554baafe870efdd11d0bef"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ab2133a08e937af16b521a09a83c5b25fe39fb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "839d21a3030d13c2f59fea3634d8394cfa97c7d4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0a0eb5cddef7a827d7d3ba947e55c04d5d74ae4780"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5fbfa41a06fabac5349e39701e79be5ee7d74195ac94"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "58ff07c4812f286cfb69bae047742a1fe519c5a886f3a5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "653a88f2458217a42ebb0cff862076dfff08ebdcef917bd2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4f64ff718d4a02663a64d61ef7a3a0b8a9e0d201c310931f32"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5980b25c906286f0850b2349b0ab1b6fdff051aac85814648c64"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "89a4ae162416824f35ef116369d155b2d941df8a3d3f6dbba2279e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e72beb4a6524fdfe06fb519edd634e62bfac05dc26e73d7da4e6b105"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "34fdfab2a60eb77a4b30e0a14d1b90c4d3fed0284b6ca4503d1e87729d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f0fe72dcc5a7aa3cd3ba068e14395b1998db37f922593dd6f340b3831ce3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ca46fb7d84d726f5011c00c379ef2fb625151c0a1f416e62c9da2aa14c33cb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "91cab802b466092897c7639a02acf529ca61864e5e8c8e422b3a9381a95154d1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0253f5487d927a5d35d0089ad9cab2d7515b65d332e870c78d1229d1c584bec3d5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "38524415a7ecc9d09128cbd0999bb76847fc812148b5a432548e4e500720b356c803"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4607a9e4ac70b3b61c47c44f9e5d05450bc356f2a323a9d2d213525ef2ad2905f82f79"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c8ab4ac86f91ab339c79bec70920cdf382f7cffa279a80687a5c27cf691cc92777120c3e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "de63da44e818a837a9ccb7d339ae9e68bb4632eb34ad5dcc2223de7b8c1dca50a3739ff8ea"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ad5a3ff34c717f1ea06334e074e30b4c57053501566d4889beb32933bc6dabd01f74d17fd3ec"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "845a8fcb16cc5459868f5200a811f511c84caf7fd7f6de2010c162c1eaeca1f3f135b14c4de356"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5ee53a681ccf1bb9d65359e2dd5daa377ce9f54096678a67390c2c9db5e797eabe13fc0ca5d4037c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ed2230ff3817d2ba55e65dc137a3ea9865e436355ac542ca0ee71bfb70e0f48f61f5a0099dbb6df616"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "29be3322662c65b7cceecbdaf27e6f65f93cf41bf27fe5dc8c29891297892bdf1adc948026ef20b6c29c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "82c496d4ed8b6cca197c25bd2fc6924c35ae9a23fd555cf12456cb24850124b1b8dce9a1badf1983f16cc5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c80125ad9d202db1fcbd9a4c7ec3857eb5578b20ef54cf71197954a45df5b5d246bbcfac43f19ae3aaf7b212"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "6d25dcdaba3b133747de6c4fae478a6feee65c70b6ca904768796aba08a056071d2853b8375cad2911fdfff20e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ce14b4f4f327941684be16178f86c3cc1382c326909d3577748c672d6a80253c7a563ff36c409d647997cf1039a6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "62c256d6561b46b0cc6567b188ce615aadeb4b51880e16f2a268cbe3eb37b97d1136089d892b5dda8f65d1e418eda9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "02a06f6949c942ddcd8a659faa3492a12f22ed44cfd58de5e4312ad33b1af337655d2b292f9e4802b5ea1ad0f2f9a2be"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c9eb45d0a7430548d96f1033a0e0c62e150c0a105b53de8b7288ec74349ed6d329b60abeb64053cbd13c97404f0b1a8a9b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "253d1710e74d36ee28918880220468da1be23678579bee544a671094dd05cdc658752585bdcb8d943c5dd008531ada11a813"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8ac3361542a33bd1ddbaa83ceb37113e391803b46824e91a81862f8867420b78dcadc8967ca6645084db367f811001471c17c0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "cdc6b82553314ed27d442d6445a97667ec94337ee913d7a6f6f83ac197286e93ad455be6e5346a3369ed7a5c03152f4e452b1773"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "74d8b9c28a80e3d2f9add23c1700a0a8677c6833c969f8337375411d0f2514757bb6dddbcb1ace7e0046fe3668f941860c9f624811"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1a801c80e2e74bcccd583037b6b2228c8bca8444a3ce7d3c47aac3647842c204c1f3997e2f0f8b2b3d63b27a9f845e392bb273497dbf"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4641d3f104a6d384d04c7a92712c02a7c1df543cddd80ad88252113c155b344530fe1602f50f0325f61669daa4b7dbb6ed5e1e3229ff37"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b79a894d9e95b0d71b78810f7ad18fbeb4bd3d84843b585195e3cdee4021a9ba3f0e6e1b960356afcf607fe3b5eab448dcf512fc5b0929fb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3e89fd9a7daf87bc75088c756067afb8da003e746553603338e0ef5aadf804267448c74e8ad014cde658708e5707976e8311881bbdd2fd3415"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d74c6b60df37362d218396f411d1ee7d7e34cb502ea637e9c9c10523f8f687c13a9b32d704fd49045f22c1c4b9d0576b3eb51f5f2b2e234703a0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5e428a29f184b93022812c39485c770cde5c3b4596c0d4e714054187a4bab511193458f7b618d64e2debbd8d5b5680b602326ed760cc5d48c5fc84"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "04147c14a73752961ae870b0ab6c3704e29c8534be4b3063adbf1430eee5f144a57bd003afce1fc1fbf6f926a34c504203ecd113ca3f2de3744238a2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8f54504e449e743a4308fb215d250a08f0541d376f8bec5f4d7afb609326795416a168084f62c187eac40f036603e8426d306a05df36b5e91a1733813a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "76c564a9a5960016b9e588d4fcad94a4b9afe77172edeff7cfbc25b2d08277225fd50250a05a3281f677adfdd96777351bd895fd289137dffd8588907deb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "25981b7d6af3c87840639621acd46ce4bce8612fe7f081cca25b72a569c81c498606deaf781f89b07534625336563e19c6b2c467033bd04b55561251f8521a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "57aa5c761e7cfa573c48785109ad76445441de0ee0f9fe9dd4abb920b7cb5f608fc9a029f85ec478a130f194372b6112f5f2d10408e0d23f696cc9e313b7f1d3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "150a3e311efb3299d0b9ca333526bdb96a0584f00b52f4e723407cc332a9d5f0f64f426fec8717ae03eaebf1b52401956595bd6e56543a53517be104be99745e51"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "6add9e23840ca283a12f0b695a39631e5e068c27abb35edf351b19e542ec30d956b3a978df34155941e11b8182f71a57f3f4edad8dc8913b3e3830989ebdcaaf3952"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9878a842d17d75bae411967da45e1e23c395a714192d424a4a082c5ffd8745c5d872a841d827865fd4586c668798117d65b293ed2950427610cb9c908bb961699a9585"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "65f1a8874e3563170d4f8c81a52fe1b27d5137056e95ff03ccc97a1c658f29fedf0f3debaa39452f14821b2ce06d349f89f6aec7340e79727dfe4609b7d08c1b5f3b591e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0d1d71883a5d82addb706c677a054c03be97972392fce88b9dfc676fe83662c2d8a310f1ea00acc7bf1303b324bccc7bd7c9b8ec0f0d3e33d6e31311ad96357b144078b0bb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b85aeb30fd301c182b980ec09dc99caf533e7ec86f35580004b6741aec28d4ca416d13eaaf1c87758f1eb3a70525932129f65528a790983b012a7620db68d7858b48f9e999d6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "bc3c10e1a08de16ce26134f363843f9243caf3bd1dcf445cfca839ee55cb5ca5f8994acd13509609578dd39d6b3c89901bf129a5bff17ffa1bb506ad7f63d0c18a570b8953a488"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d37cb91bdca013430f38c914d1ef18b68dc38e5c6013cf8e357048df2c86261a0f3301bbe436362bd81059c1f315cff45a9091f1e1d84141f63ff92f2c56a6ba11fe3db17cff3377"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "52704d061557de8f2debc6cfb71616bd6ea10eef41523670f87e8f8acc673fd3100b063b95f8bca943b3eb98984f908142d6da9e040aaf93cd711191d00ac48fa56e4669d2e7e5b548"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2c0981b3580aed2add3f7c0167f04221b376819ff5406034a41c261ec5969ff248b7a0ce2a6c9f01f1ec80b7d98c148a3a9f842c626354576c9e6cd0588aa129cc9360e9aaa8d4c58bf4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1b9b51a0e20a02922fabb2a99f11c9ab0111ceda3e20433b25caf010190aba37789c996947cff081d0c6332bf2a780d90c1ccaaa05ebe9a2f186e30b210f9859ace8bc9fe84bb5aa512e8e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8489919afae8ad867c55bddcb43868a089cdb5ed7b8fe0a3dadddbd12cf6ac1d608741d76881c085b4542fb5e82959860b4d617fcff1e627cc89910a8d7cf848dd6b0b70c9870005b8be5ea7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2b820effcb8312b6c05f1013d61327f84c1f11c5b8834a7e59820bbb8ccf77990d0190fe70f62bed946605d82e66ed4c68236c9aa39d9a88fe668331dacc607a3dc4a30365e9a185bf294e94ce"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "481f56240d0aab02b669e7e595041ea59851a8372b375bd1131e39cbdcd76e73734dd5838ae8ae655c2ef513af9bce364b103911defd332da64a1fe9a11011195e4a71c11e8eb57d82d0457b0346"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b57d1fa9332b7a22fd6cb3348733c2883f4a99f4a6fe22239dea9320a458f062a391e240044d19105b81f3c08dc9ecc5a9f86bc884cc1bad649b9cd5ce12a1f0a73bcfb5c1c32dbcbd75a74f5df617"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f59fdea0a8b6b99202c455194f5bb65e56fb45d34500c37a7e73470bd1175714969b608cb7507e8fa1b9e39dc82b1582b3cd4b193e1f518f016a7251b6f52ff4b1d217758715b739eee27b1c9a2aed11"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "56fe4e7c41e9ff59c48b6200d17e6f9eb30b6d4d18154bab7db9aaf206b667e937cd3d4ae23916dfb9f1485ef68c1aef8fe7a661c6a5fb9cb8034364821641b6ee9d76bd4cc378a454435a98c71e47ef8f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c7233615cf8b9d6871fdd7c0024f5ef629e00b2e0c0a19abdcc7b789ff1493d187ebad63b651eca605f8295b29f01364422527a15176754c332f3cf566fd2fbcccfee0bb33d9305e7b8660f81f3e8f6d42ca"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c1bca6f43d40a287f596e4730e27fcf56ab9b841aef9e4daa4e244846f2036689cb319520b93a43aac9e9238ffc256f26e7e5860873cb3404417099eb8147a5e12a094c3d9a758ac989c0c2baeb1e719c2a432"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e3b82e4cceec32977767241c1b1e8504ba76018162de5cea14a19c5f06875a32fff9e53a047e68416b8dc6bcb9e991487b81b76e58428cb33f92f637965f9ee00ec57923019e702c7ba3a8b3c1b7049f3bccdeba"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1489fc59f0b16c2f6bf4a78b6cb71a4c12e0a9e02567edb6a14ec2dfc512687113a709d7e4457a8d03c9d3b1a6cd77a0d50ec5aaab94d48d366e976e7f3dec356ea32ec0561bea07ce8a4204911619ebe54152c73b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7cb2babff9c15db475ee00db87804d984fdc3be81272a60f8286b7ccfc4294841908c56c7268e80356b566582ba457614e392f58613c4059b184e085b0caf4e81aef8ab67a2220c068151ae100c9a323792d7f49e477"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "508f70ce1e4450291f690e2172944e5008dc6bfc494f891a8c79c012351ff160357b39c69027859bd0d6ca7d329ef4c5779e8f3cfd5921ce9c7170038821e4ff03f5279a01156299cc5227eb8a81cae310d4c7ca52e7cb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e534d9fd2632c0d64a3df145048bc674ea0f01af879a26f252a4b0cf406d8bc4c2f134a86443228b667ec1c0c1babb2b04775b064a3994965cd6080913036ed387483fc9295930fe9ebb1da87adcccbca8318fd2b1d14e29"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "deed43ef976409050873e966a722922bd2b61bf11cff0064b5458b9eaaf9bac20846b7f20606d84b0429bc1a0c00df5b742cec643d44659b3243cf42217e6abc84d01cd326139120f96c92f625a08dcdf193639bc5756e29f9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "00b322e73e6eae7de84425961f11b9736806bafaddf0495274552328c6b1dbc9c88227de05e8348c04b9fdd9e9898fe69065cad8f884e4bf1a2eb821ad0a3eb4b49cee2ef299a0051976b28af1d3d16777233b75db6b9004cdbc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9d3a5ca80e99eefe49eee987132c73bb973260869149ecf8b167a7a4424939f48c5acfce848232f49372aff6ff09908f943768a5b87bc30594ad6272f63b5ec2fb46e742700767d4663f82a1ac88e060ee87dd30ecc27115f52df1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e068a4c054cf8b757ac81d216c961f221276e1753148fc9202f83f258bd2877d192e324784fabe600a2dcb53996d67aee8ee9964b9020e2bf458daa4e9494a193f3446daf087899d71c52688a14c05f5ebb6811ca0fad85003aaefe2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0d4ea1a13a2cb5e403caf018e5c529dcf0fb8b1fa625e150fa46383c45fc25e51725e946f09b93877ae2058de3577e996637817a3332c46842089ceef2dc9a4f52f14edd2f10fe56d11477a4eb06e559d4636c4f06019be3911426d1b3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8de5e14a26cf4c758fd43b30b71fab2a77480ee98faccc95488849a49fb983362f883e5c75d6e6515063c15194f3fe9fada18b91b8ccf0b3ced07c410d8eda5f0956a1fe9340b5a3dacc10c2f1b5f4a9c751a65361f21273a11e571829cc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "bfec8b58ee2e2e32008eb9d7d304914ea756ecb31879eb2318e066c182b0e77e6a518e366f345692e29f497515f799895983200f0d7dafa65c83a7506c03e8e5eee387cffdb27a0e6f5f3e9cb0ccbcfba827984586f608769f08f6b1a84872"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "291a233932dca80d3a4ff11f06c056b8c7e9de2a99c4c28c666965ef6c075b8d90017608ac7074b7e4831536f5811ccf97f47ec39660ee1de01e31161cbfaeb575a45413df6a9a69767763a4578c23d1d697d7b8673d2b2dabb05dbd506c7c62"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "967c76e3814a1a2fe4ab811b8e0ec56b54dd67f37947bc3554adcd143d8ff17f0f11b5736d512dd8966bad9c4e4c2aae5835ef5d0baff5c6a034e58e3eafacaaa0d0ba4489dd78f06449754b25688fd9e50a191bab8ca6dea5e59c08aa07f2947b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4ea13e136028596e6e0ffbcd2aa8f177d2e40abb7a5efc95a66b0113eab8eb48c7e0af7a7499eeb6e04b341a229f24fb5b9c6ab444288d32a9489e9c9abc6bbad1fcf406adeff9b14e29bc60dc0307094ad8e6b1b6151d7dc185c2a6b2f2c0465798"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "61aa1f4d4e2cdf23bd547f425b52cdac799ff437ea49adbd5a81f0801f640881a09569fbc8b6c91ea2c5538f518e054b5ea75dd074a8285b5869b109646408621b64f6a902ae061192f0dc25a172361508c945e218d1e42182abc21eab8d2b00c8e308"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5e3f0046de3d99d5de3d01ef2947b812714e09af342d9ea03311565a748ac0842540e0504aa8a54d4c7563bd8948d36177d88cc7b14777b2c7930252d4ec1c1a0fa0e21ff2889f41615c9b828b179c4778f314751cc58fbe386bb6cc48b1a729cafd9f2f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "37238968e530a2c072632186f8c54f271d15f43d2bb2a5541914a9d771a7d22a2e718992f74534da17f126e1616c39788bb4a8196e49da93ff4c6300b0d873de6b1effa0af995f534ff4c5c079324e66b18d3c2a87b632541a39c1353a6e2c0cf5b594d4e0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "88623f66c92f3993a309c6ecdafd29815c8b9ac1757290ca3a5f5694932e57acf70fdd83c595858b3331afae7de0884859ecf11b28f84ec8794fb16a136ae0cc9a4360f64a9dc6e8cc5160e8f11e2d2243e927e1479bae5afb82d192b44e59971357a5cb14ab"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e8b11e720b0870db776a8f682b85c865144ffae5a7ab7849bbd0cd9077e5f64d4ee4aec0b25d06ff5d2ad528b1248df90a3dc8cc189cec026b22910d57d756b12153362001920c3f82d102f910eafdd34b1a50e9b99b019107e764b5b8eeda5b465c755d684489"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "690501ac0b21e40128f36be64fd19919dbfb4e0edcf01df9a4d946f1660d3c81b896e77fdb74ef601e44b4af6e1a0f6aead0fca6328542dc7c99d230ca97b905bcbf2dbe0bbc6a73f062d555fe2579cd3e658ddb9a6959467f64fc02344e42cecbfe1c10740f3b6e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "672a69beeb960495361d80ce9bdc3c348b8a747ae83399cb648a720ab7596a8c2db14f924c0960355988189d6aa437e87c3ef3cf3a716d49a045a4fa8dcc7731925f1d5e66f8c0e644d9b4746f5f27d2361269c1313f8badcb38d74ece67d4de7dae120dd9f8291393"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "739c7ea259cebc61598b9630de10c1217eb32a1107933b8929ffbe71e664c266f5733b94843a57f92f57af363ff73ba91022d92183ea368ed26a7bb27162b66651ccb4c3fd417b0ed195d2b7f1323396c49d82f5fd17c60962f795705c3f8262cacff82a587d49c25b37"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "fc80be54afb6a6dbfa21744fa588249e6532572e70236ccc6170f5abfee2e7806104f2ec507782d93efbe7b9f7b098588bfc2f62879df7b05929523015cd5720ef452c244ec212bd21ecc3e1a15932858dc6a55b6888947c067865f0c34f7e8b78b76c8f18c28e1a0b81d0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7dfa64ccdeec6811f6f67a899511d693b999cfafe705a9cdf0458273ad314d2580e01d6975a476beb67e9cffd48da875aa9faabc555968ce2f3ad0b1d9526c986b78fd86b8abc36e281de1e4258ba5521240ac7734990de7299e51063344d9bf9fc1a47d8a9b0c4b9243e7b8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c4d84255d5a7ebb77d632d59f998decbac3d031464653146adf5e78475910cc049fc0101fabed376bd8440bfdffd83d80c27081d531b9fcc3e732248dca2c3bf6007da725c0787ba07269b05034a953a2df682c962e1441c96024966ee6663853f8c3ac88e80bc6117b7f68483"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9003bcbe5e928e28827eeb435f311730e8ab0e7d1f2bb615f4258e591c625d28301ea72520079c40455a9464e4811599ceeedb913071f5c9c8b4486079108e0916282668799441c4901b72d5a57da1f72b65a1512a23c4c0c88857ada847d75c39f3e925100ef0bdde49f5127083"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1c1ce518a470f751be5abc5c95e797809b40c83f5002c69c3e34b2eb52e9eabd4202539d821e98e8510733e26dad712527ff04e17d37f9172c58e4af94524fdf0bdd55126cce429c7ec91293d073a4e3d33b33a8bb198b2bac7113799cac70df1d860db405b78a0bac420976a6f6d6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "23e5ac8c6331d772e7b2bdd3f0b6fe57df95bee52645013072f076835a3393547d454d67f3f228d1cc2d1e969a80879c241f68273477ae65a6cf6e5194e5ad6cdb2881b74fc0a3e31263669bf5f1fb70890c681c5e1d12eee43b44141e9b95d0180c11aa95c0e513a96861bc3944cc31"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "51f3254390ba613dad1b4288b6b40f8615e047e1ae85b7eca3d1e8fddddb5ef0555c1db8683fd889fc304c62c70eef2ea4c21c2192f6097a996d421b8b6f730cf829ac14f82b45c64c299831e8015039a10314896517297c4c48a91d0914a6da1eb7951e29d0ec63f3fde0c98e24bf7ab5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "75168cde1904184c2cc845ecdef73f69fa67a709cda969853fe8644c44cf8674f13c3a399509f1671568edeb936ef60a450c282aef04086bded69d0696df84b00c3d3477ad51d5483cdade2eddf8093de0aef19761f7af4f6182bf48e848b422088a22ab38eb331c8908a2ad28956e4824b2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d8f6542eaa90aebbc9d24c28e29c229ea4cc6a775aa8146e98cf6929160d90786fd1168e5e81aa91246e4175b06a383f3cf61a6d832b91c13ca2ebfd88f659e351da333fb25ded1fc44df314cb42897cb56ed6544fe556d4e6a658fead6154b3a1cae3e50be2c81f2f86a326515078fb8cc910"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2be8d7128e0200e78ccd7cf6e61a8786b62809b2a17fc3b333387b2205794fef2b6836b19eb9600b8de93aed934191b3e2c291954244d46e87774c465f96b46ff46093d66d6ca1ac4176e9e59c6ea649ea174ac197043f6b2b39ab3397218e78bc2bd79071bb4ab3532d304bac5c5e47730dec36"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "835a085c2f477b537c8d5ff4dd439f2794f8cb3bc1d787fe30b3ff9c590c3d3df6f4b903238c482c63e24b448fcb347b73821089496fd7587f4b9f23ef73c615060c9282fd67f8f012869ffebc12521cb745dc318b07b48dc02aa8b5a512aed4e346919b3ee4836cbdaffef1b3dabdb01bcc7eb636"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "6c626640d1496181b0c161e28ccf741598e521d9187e8ac1a648bf7128dafea2562a6e9010f2dd8fbe561f158a0d1eed7c7f1ac2cd208b7cfa2c352939227a12da50018c54cc44a7146ac79d5d847897a69cd64e22f252abbb506d141d3a8393b38a3a3cd99096a3d10037de1feb6404b6c5072c3d98"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "295ccefe2bd4e68d533fe402c8e6477ad00a3cba55aa1c99b7a9bf31f0d021d761607d27312fb099784a456efbaad30fbddd354df45c328838c423f169888c3e7ac7dd9f9052ea356aa46171156ddc645dd2d9801aa23ca832c8a19c5c2fbef040b66fcc579bd73e92e0a1052519452392c94b50176012"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e635d9a82e5fa5af49561bae9abf31ce5c2fb85c1d3ed819f2aec39019bd637d66595ed7be4d1eec75d2f97ee0bf41b441624667e0e7a727e4aedb3c306c6903aba306b88074d2c9ae6787c14003fb76408efb90b0827538f5099ea35baf2a1d7c874ab38804efe51a925aa852a9b4e776e21fe913518afb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "92e056584b2767dc7a5f4f4d0837c034d962eb36e97a590fa3386b106a58e238842923b8e688d6034e7c1466cb26f2b09a5378117a8fbf3fc08398070fcb4ab8d0ca0b4ef197a4252ecb5eccca097d32cb036c8ece264a45f9f7d4b0ab945fd95286ed3714b9979b0575226fc52c979fc26cfa8d3d23e6b327"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d826daae45241b3e5b24e9049a4f86c454b50c5de06e2dc70605acc8da4712f9391ba7a8ea90449c368590881ddcdb2d37775004ca032aa8861453704c9afc42a77553d24f664db7798b25667e9b5e3d04287a35fd6e604feb70a2f8d03de083a364e7711687eddca8c3b73e0134b940b0a42f9631a74f39cf1f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8cf01578e2d2cf15680485db397ee230e4d04171c45fd8d0c65555128f6a13b8dd28cfd7d90807ff4f71ab9ef5384bc8160c49a23b19a541658d7b8f8c43aa98f09af0fc0668da332b1b8f9590a5a5b46fe9058bacf25157892705130d8004dde4eeab3255214776a2d9b2368755b20574b37524f15e300e0aa093"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "16ce0a26a1985633c5f9c653bf4da9853b301b08c321165e20ab38762efc7952352556f91d109779485cab6100294f3d59269b99082037fd5e48a125523c95d5ec4e8339d3eaccc67d9bf37c7e82c0962d72532b0448a4497d312524ee2f92b44a6763de12996164821f0f11db66031b1247cb4cc1dcfbc366b93c88"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b145e964233d8c74eded80b6c625de7efe274142c26ae9140a762bfbd31bf543eb8e52c65fe0c96dec46a02c08a8fac0aa564f0abdfe6ce629d5e191ea159165f351982d51f0d64ea434a1e7e789ffef38ecb8756485cbc2614de9b80e1172fd2c4be05ae5b7dc76182f8d9ba29106e5ed4f8d610c7708fc97eccade92"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c96773d31fb8c6f02fbf2a9f4f4ff74234e26808d7162678d25c931309209c3b568fc2c69b5d97a00ae7378702b344644fe9c4b0f9e44fe01ee219a3471866ee1fafa8b00265a10dccd1b3f676562ffe2fe43bee82c4f7b5ff5e04dfd4e23de75b6ee35dfaf01c716c0db1c848a781d04978bba749d347b6e85c5334b74a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "87776d4b3be82f3eef9a88f8135a005e4c8efb34d51d182efc358af1b4287b46c8c16a6307fc57dbdd7af3af57c3f3670a310e65c69ff7c91b2a75bfe622dc860cb6384459eafa243d0d6cc768add9bf5145e6ad393777d745f285ef4e96f2238da3f7416b049958cd55019850982843315038cb97f788be3aadbf283ef956"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4d1f33edc0d969128edb16e0756c5b1ef45caa7c23a2f3724dab70c8d068cfbfc4ee15ca2fa799b1eb286c2298036faec73d3cac41b950083e17ef20ddff9d55aa8b4d0365c6dd38d5ddea19ebfa2cb009dd5961320c547af20f96044f7a82a0919126466bad6f88f49b0342fd40f5c7b85206e77d26256c8b7ff4fedf36119b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9339bc4d2c29865f64d1b93b1e29314bc26b78d53b69873ef54205b3891155683f2d8199eb9c8bb37becb7ec02d126b730cc4310c9c70785fb819551beb509ec14f2f7cef1deb83a8c9fae4511f94d48ed38f8a444b73630cd433ed8e6022211286a2df8193dbaa1f57b38ff84d6ac2945a575dfd4804b538cbd33800f488f254a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c4018d36d2f84fe2e1f726181caf1952f3501b4b4ababe525c76d8436c9e79d18094a3f7726f3bdced6781f1626e90ee3b230bb4862e5497129bb58b40d6717c7f25ca7d96f80b1f37273105acc9dbd30f15fc0c5b81f465e2f596948a00aefb9d976eef60669e1770cdb6beabd2ba112622734c8659eb87f0aa08c034d462a0267d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "03a44e84603f79ebdeef6680447ded2bdd5a640549f4ed6cc493ddec1006d0535481417bb8ce221e1b3b2535cd0223630e2f96a8e47f44da8a998c2766be89b2e245033a1bf1fc15f506825964e6c6a9c6c5eebf06b1fec66b8322d1425755aabe7391ca9f5c59cfe0c14095036b141f864c01a5dff04b8fdfb7de2b88d6c31f8c684f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "181870851c94b7b02629c99ed1bdf55a20412534c5101918cdb36525364b1e7d0f7eb2b4f549be88bfec9eabe45be9e4bc3d8495258b83c6107c995c85e1f5ec84bbb8bd43f58ae98d13854ea7de75e51a5f73e24bff4e6dd850878b3b7f8948ff99924b9706bec23fa613634abe2191023d01d463ea7f6f4019f1eb4af6e74832008e69"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9b8d7205ace31b13e544de5f65ec17fa2ce350c8bd32e68aea7b22cfdba9e0181b8db8dc961a279f3441e2f86543e0ef8de65b89d28eeac748fb65a2b5c13466ca94fd45c425f8146c4fa12c6705e6230b0a0ea060fafeeb43907087682d26a465fcf7f21ac52f1351f73b45d75ece0680d27db1be924af3a92adc38a9efec2963b6882b8b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "60ddbc9c10494d540b04627b30dbceefa1331bfed86ea30407c99fb18a50f5afe58a7f73544f6c70b3034825111fdd139dfc3e0c3c00ee2f1960fee71284332f727efa6187a4d131d4271e23948a2c2092b24fc0b366ecb11ab464bc85e2c779fd3d9f2b68e62ee59d9b86debfdd96cac33b2628a5a966c26b0602e242ed4053737a3db0e83d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "27e3bca4fccfae88c9a7a7f43c0b72a302b626cdf32eb70122e9cdbc100be2d29d945098f5d74354b4f90a66e938ed6eea3ed9cadc24a1b08d12e731150b327f0f53ad43fdaa9ba76785c03fcb9b2995cc2ea67009a0ce10e92f9ed9fab54c09b3f90b0fb9db2545edb9088f93a547333fff31dec6000120914baf6b6ad7feba9ceee614beed11"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "71028d445c4d77fa30c5c58057b4d233ad2e12ee640d484177e20c635fd1ebc150d1a3e4d855b7910623492a6828c215d5d1a9f39a4ff3f4e6c98c98068fb1f3b4ce319658fdc764854b95928bf0315a81a0f0a022b3d2bd285f22b16b1613b52590399f1b993589ff2f8997c4d9eabda69c9f3b8a925ac9b4942b380c6fe0ccc95f1b4e3d8cf336"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8533108fe06b8ffadb5b8618d03486db657bc32221488b7b629d6ea2e24c1e1de3755edb971c1db135bb38e0cc943bc2b3aae2369b3019b98b5a92dbe122b35dfbef0620685d5a808f21c31704e7296f207dfda914fad3a903d8142c623a83a0f55dbd0313bbca2c2831c8e4f02b10bef62ef321644836b3b5224a0eb68e84f25742450b10f9c59bed"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "45a62940d954283daa40a17b89a8d89f383ecd3a888d009b31d96f48bb5a656e4d6870062b41ff33d0b7dc7893b3a480f2d2c4c909b18d50365451e7e56c6305d26e357cf51665bda819c1f64b59dfe87123755523ff969934f5900d2f5e69da3189c118ac2bc1c0438f14b1a37d2227801ac6895a3c54dbab0faf77e9b1b8eea879c9dcbfbb7dbcfaef"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "03f992e29cff3059d46ae559186581b67954e6efa73d7a8aa26ed3e453828c3e8ca5031a988dbd680648f5775b0484a09d0ec953c49c3f44bbeeafec5d7c0820e6c04cabab144d66e2076f1dc8ffda96fdd292edad65a671c440a2810bd0c86e9608173ebde1d193f2fc738e1482cabea306208b9eae348113be4855d5aa322ea604597cc0793488b65cea"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f3a30064a77618fcb495df57dd45d498e7fc9edf91e6c41fb76ab296201f6b9eecff62f5a3aa6309e38d02bff0548238dc7a54bb1c51f89500c8cd07bb8da92a7659cfb12b175f6417eff25d811c930b029c543c4cd0f849e77cacf476f3bc690a169e1dbc5a60c407cf206074980d265a44231a9afe10aa679b542f63397019011e6fdc0316f658dd10a4d6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ab3cfed6234f8bba8918664f83d266b3876ad57575032b3260c3cbba740ef1530db08f0f5e9f1718d5bf1f177922407ed746455736f21016994e0b447258234921f0f373974ba0e17f04f0aabdf7e7a33d180e474fb259191450ba590e225e9d7fcdd7fa5d4b3eea4f836444e31ecea28727d215709ba33bf6f2fe31305689600a314dffc81f99afdc1f63010f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "fcc8d836915a460569d081121755425439a95a85f635b8b1087b55abf43c1ae3af7350de02a0d11443315e5b46cd2dc135c5a2522801ced212bbe05c7c595ce3cf30b9bca9a34082b270b42c02b12e3c570524a9f7724ff0adfb2a134cfc70b6da98c375f197cd1a0f56c792695a88272ab3c30fbc3d46cd391303812e3db840e594f59b25e21007fbb7e259ad2c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7664b3954ecad281dc23e0dac62536a4512076afd536addfc74d2ccf9742e80abd6a45dbdecccbb951e41b5f9dc4b5c10fa2734a0ff4b247f237950367a3e2f1ee00f75813eb6c3d6da4383ab908333fc123d5570a43319587d9625516122104e5691a7c6e9244ab7785846e13da5bd0e15568eea2d491cf202a0253a25209389b27c817c22c5ff3da99520cd71290"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ed5ee508aab550984e5c58cbdefab0b5161a4455f2d4eb0cfb633dc2fe88144504e294af91dc830604a0c6be4583b97d054fd2d11c431292e52e559206bac1a447b4267a93740c7fc50973a6569772589286593164262056ddd60e6cde4fcd7798cfcbe4761ceed93c68f9075c6f1d5ded991974c2c75a8057eeeb73eeb07de5dfdfde32af4f69fa1476209f38003cdc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5a1c93e287f1e01a6e832e7c0c23522db04758c67faf12f8900cbfdf48cd62fdd0e5f1c1de85c921104164e35ff970ae0f047ec1ffdc84934cb869d4f944cbe010061b6e1f04fcc67eb1fe73a267c9e2cc784937b70ccc9bc210ce12c52c1a4c6e944d212d8a7789f7fb28e0633f299bfbb69bc967a464cf18610c695029f14a9049fafd3d876b68c9e3abdb4f829ee2cb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "70de86a6b3d9cd1d9b61084e7008b948afa2beda8e6171618260533d5f55bde09d454add55d6c6419467a57e48fa5ff16ffe852d5d650659310e3d56da10d0bea2d3ca1ed3557ea2ee5cd9a1067f3415a628aa0c174234ae0636e662b51bf5d1d0d066f5900e7464ab741bc69d3fec621dc610748271a672862aaf3c553fe4ca2ed9ba4f57f1e27953e3167fd1d9c3e4c30e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2bb9490c3f07c77e4cc4db3b10e57e6606920d0ae01bf3a7b68a3d29b327ba9f5d7389fb0636d1494c3cb95d27122cfbcd9ae960fa0fadcbb123927f93599a24be52a01be80f5763597b8c52268ae553f5f6a90894573cd0a8876ee1035a65ff5a739c8abd8f115cab980d369d68b266b7a34d75f9c18b2efe3742e3e398ab6c70448f670354a8b486fa916bbff492fc98daab"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "deaf859404fc6e48c66cff9bc437eed861b45dabdbe625af4a82353f426a040050ed9235c0798a40930714df30bb796b64e9ad0df4350e196390aba4b0a10a6cae34007414a5880c5c2ce672c2b36a0e7d2622d677b74348ce07eb3cbb1c5a9c568063d8da555a64a0ba4b126a0e9d9f9ccde4cfca6d206f54c022f7518b8dda9d26d31a7dc9a2afcc3abf70ee5b0fcef9bf669f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f875ef68c13c5a982637b84eb21a11e6f57ffab07bbc11e5326fea005326cb27c6f70e99347d5bf3b303639c260193c1b7d15de51da7e661e2ee4d08d5022d918d0bf1f3a4572d8003a8a4afb821f6dd03c2bc189391f25b2e425b743282f1371d209b61cce95092a2aedfabcc842fcfdef7a0b33473992e13fe0d6da31b41976597aebd26657407ede2d7c51ab1dfad8d44e66b86"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9f46e27edb697d90398d02620b06ac8d49a3f66f0cfb02b9c628e70bfa0bda51eb024376fbcec59d8517f1e896557349d22ec78038b66a9a16460b8941005336bff96caa32e195abded0d31de0f4c8ab234d994979dc2b53476460abf5136b6b7fe6489793c14c48daab353208e150c43f7b1c7d659d6f8b5bf2fc3663335495e9aa47537afa5b5173aebde200f26705b6f1f3efc4be"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "35a3235938ed1312e8d79120615daa622cb5d24bb84efaa84dfd8daad79311ce6ebd52af7c8ea8dfc3ab72f49ede51d5b62f538e6cc84127a0eefa20c558961c644ea09c913cfc8550173109deb8554e0a418351073d5bda8f13cecdc71f32efa4e8c96ff5b30f663c7a4112f4b6b6afd6ab8daba9e2c7ac604a7e4b00cb52cce8ed10bca0cd6a6d1842b59867ca9e7f36ad678d28ec5a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b3a70cd849fb5978199d363881831cc9df46c940d405360faa294e36e8ebcbbfdd8a672dca3b3195eb86b1a0817e537f853ee599ef3058692bcf5dcccd37118fdd42d50c524bc96ba7c6d2deedfe062a32b7ac93a20a8bd3ef20fb7a1766db25313390838e6a08e01cc932dc8b4410efdbb02aa4d53cd88d1b5fa0ba564b6e764aa417833f52e0a3ac46b42f4da1ddce5a35e070e0e02343"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "090c351eadfa98ea2843c39c0f1f6c6bc1800cf17359ae22e7a8a24960ddf137666e1cb45eaa675bd01175e81110f54c775ba952e3de3784987c96b92bd9fd349c7f120de13553366f621e7437a08e374612dae5f9f2cf9081c6761bf287c7a3af39963f06a345f1a8f7a8b04d360836f172bcc2f4b3aa2c6754a60b1bd49d42747561b3fef35501581f606777e6530c446daa462a4f7ccf83"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "01cba0c30285c6963c3bd9455713eb330bb887289c2a71ef7755e64cc0067810f120a1becf6a0dd45eb8c31ddb2befae75bbe7b4fa7dca9417455b92b6adb7118a36bed55b379f66d9bf4423ec928f14ff644019a6b9bbef69cc013f717b9a385ada55c87c06dd6b3474020edeef23188d0ccbc5d758e61eb00a20f9f52f309f2a8ba45f49db93170e61c239eb8bbc3c89b0b925c1c87a099aad"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e13f2c5aff51c441fc9aaf48aa45bfa2afc7c517104b6b7d50a9c4fa40ddd0595bb06541ec709c685aaea4c77ac37afb266f3510ac0c8c8891b4904a065ff9f7dd11de4bb758f6f2f5b16370a3ebc5890ec75b69a2c014bc9c67598c7e094c8820858e34c0d2150996dbd1c3de69f53dbfae0283412d27680428437ec1d5dfa444c5a553851bde66ef3377019c06f93d36e9b7e540e09ed223c948"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "84d5ae7fab040456d5b60a49db84184c6522c671eae6d715432d5b92da30fa8d17ca6fd8e10f00dc0a06fd7bc0fd3c1b00e6308b0c2615b20aa1a65d8ac905238c0520774181ffbdf4767eb2971813de6ecb07baad256ae3dbcb1b73d90afd0f5de22ee1e4be79541bcf4eb887ce1f904c3f32a868b289a76e43214ec171bc1639b51132a1f6b0db60c835761b6106115d81aecbc385114d56372891"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5fe779f861e709fe51ba04ef56aeab3865b758ddd56caec49d268eb04ccf2cfbd83075286011ba7af59b8c50f81d978b029b541a5a5023c86533e1d25c4db5ec4f9b4ccadade8f4332fb62dd5f41cc0756adb4662c4b9063c7bca3dac7719082080e07de40956a4f3922b0271d6227115eb634639383608693bde9942fbeb4c0a76c1efa407946ad440556f489800d92eca6a7c73beacdac79e7fc551a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "be6ba46b695d0b8a24d4886e3a49caa510bed696d342a842bc51c0d015b9b737f299516ec27d3082a8c87fcc92bd50efa4a3aae7ca140bff820e5a849fa02b421fc0647a09f5a0f01bcc31c966fefab1e136b0c6d1fe1bfdb24a436f7a84d3905a88dbe18ceda4ea4923ad4b2b42ecf1923678f3d0bcca46c20c4a8edaed8f76e27ebeeff5d3fb3831de0b0de469e73968f94adb172c50eed38cb058ea62"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a00bc425de55c2d030ac39dcce74083b56f809c1f1f8e3398fd68f4139a083ea7261f872378aafcfa6bb45b720101c9be64eef0eb2097a1c4f66ce4b56df65b144ba74267ace2c0dc80076d5d3e6e8a9acd70f21e7e743422acfc148be0088db05ef5575f9eaf5a1bcfaa468142b8f2c4a62ab3247571dc2481254ff030ca298dee37a4500845ee322378324ae2f635f4891e2d46f1f9c71ca307e5243d056"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5f57318fca8c3a3b590e0205a5f5d314c15fd2ae751ba7befb91c4f898205f3f2701651e23f941c274b9228a39b598b33405f4a75e9d6600f1192e4f06f81edeb7f619ecc4e82b161f0cf9c8d37e68c7139ca9d653ee34a43419205caa61d14ab51f28b7c8e393dca8d6fdfdbd796d7f84ec0f3f33ebadeec67dbf7afe134c37f2bfc24a8ec47e0536361a87c8ac238e42c0baa9eebb9d576548c847f78f3f78"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "61237c1ea7a82566fc6f76a1d3d78eec3b3181de76065da39f4bd1da971e40e3011334c225e66ef4d4ff50e49b331ac39b00c91f985aec227d43f7e0eeee40c2668328201bc663039851fcf757e1c757f27f831a983b1050ac1e669c6a1f4cb42fd76c40f76cf0f4bb889ea36c02890f0d2adaa009d47169a9a8b4d1bdfb2e107c7b3b2bc907c368c59ab37ef54f84c4488ab75901995ac8430c27be15934f0de6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "24c443acf04ee19b20fae2268582993fbd142d22ad75a777647201116bd8c123334f6e0e847b3030af1482f7cd2cd2ccf5425683172094cda8e73e33cdc9991ca69e3285b9387beabf00c370ceb47db606aeae4b958636dd0937ce857cbdedbe31cb67a33eedcf2c51dccf620e20d8dd9f35718dbd208d865f1459e4cf3946e0a9b4e60ad0c450ba81d73d892b0cd595a52f4e07b6f740bb957e7768a9f32bc154ab"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7ba4f3ed6000a2117982962ffd84e932de959aee244a8096778264fdbec4d04ddacda8a73c5728b04f0571ce5b9ec482a9ecf50b21133418f644262d9794601e7f2398629122b5b888d6af02ecb42d2e41238e0082fb2d47636f2aaa6cdb7d3be5eacdd4d4912f1b307e49572f1d657038ca83d1a9c456bc3bb6faf851687e5cdf4977984a44050413b5fd2c4272c2a3448fcda152e2c306a98d738ba1f8a21fcbf57a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9b16ea160c255452b6baf3c4e5caf627c1f4e8663da9c9036f048860d31f0918075e3eda569254422b0295ff3afa152e4df0f99de21bac416e028e2a32e52896179f18bce7be2c5ecce28625222dcfaf2cc503ac4768df7bf04cd38d6dfcf1f1c4233051ae9843de7ac552203cbe0afcdee913ced89103a986af7fec428f9f93167a4218d5745cdcf57c6de15bcac08ce8a8de8b03ad9ebde73978a6486f7e1b90d1123e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1dc2f82fc83a973eba60ee0ca1ad7a50fc50e9db3fc530378865b9abd4101198d6017a40777839dbc6313ecf35b7b727e0311226fa865d73a43cd28a400d8b35408d789b2249b7a8e5df452d50c6a3c28fcaadb58b222261be49aeee3667e0ba7c5f2726e31cd5bffd125231116baee18bdf7727ce3bcc0c31e1cf600635623881a944d14d514e1becd9bd0d823b41a93ae969396db22411172efdcb73593fc8cf80071f2c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e8c00940d825a6117d59d922221e01e2dfc2fcb47bcba06a72f09e8f5969bf988aee8a4035fc97d4ba4a0d4629c06912c908b90611c1cf4c580f8bd3345fd8b155c64dd747a9c21017146d620913979ece6df5a610d12ebb9ef30724586655ea7a11e45ed51a58f7705db538dd075a6a5ff70239e88a8d8da0817da0318aa57f637b522c50418da50d0710ece7e3b36331eb1147094ea8321659e6cfbfb90e92f10e90c3a73c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8e9c1aee76744af025ba3f5bf630c13af132ab7c55230c8adbd20b214f204494f08035d9cf1beaa30a3f8522eabb88d02d5558e7f43c27b58c56dec5bf67812055a99bfca881e86f0024ef84501eb63bd5af849a134de4dabccb087a6cafe0426ff0d03de8cdcea2af83746f94b33eba2dcf4de7775d6a4db8d0ccb0d789e11b9bc3586e82e31a265cb26f82ef705139a5464118072af13f494f1b9a07bbbdb19dd596fdcac414"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "44753f6011c68059b5fdbaf6156e158221b5f1a544225844b33f3d4509efe0d4185a670627c3652b4f6427b583ebc5d941da2a801111a12d8d46ab1c0fde95c91e538be2e86954be14d3e7ae33d04029102e4267f3e2f099fb76f6801ff19f26b5ba07c29ab74cf1e3f3e3bdff363699dcfa7a32e3ffda3419bd22691cc64167f66a3c999714144e1079e6ff0472dc218d11274c061d92d97ee5ac1e02c104d1313a1ce3272326d3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f6a4be7b99c42b72c4e03cef4ac7420b445fb0720804df5f58d284e16cf27ae905dcd5e53f2ac1cc19d62c762fc9906cd45a0a5720dce51692285118f80dcd0cfa4008730a49c69aa259cf9221f2a992c21617e5fb361b03cffe6bdc283866d4f4823abbf43f02dd4d9351f1e37cbabe3eba1438dce281d6b87fcdcc88f4d6c2473d479467e13a5a1e94d1fcc32b9932c5f28ada66615d1f8c7e6e9170b06ff1c1041eaa4ca838edf7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "894491aeccd8edcceb6a6cb3c7722b2959cc591f8443b1efbc19a459d84c77183b2439e594e644b5b66091059fe372cc41c98ee01e2b83242ed2379dc97cb7f9d75fa146b36dfa9f923f2e5093b19fa4beb18d844af8926338f458d458f7452075bd0882e70570fc9b74cb61fa4fc2d50f8aeba4ee7d1dfaa4cdd44134c751ef6dd04d06f4892256472a09b9cf68919769c3b1ccd75c3b89b7f122d9b5c37d8745caaab703a32113afe7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0ba47fc6ad5364ed20dba3638480fd26ab35fcc2da8220e971f3849235d6e706f291e08736bd2edb5de04299bf2fbb97d9fea02456244540821a7ecd0f3b87a7819e054fb14130c92245b72b8b8b4e5a0d3a0cbf513a75aeb398e2c5842553c947297603cc733021608451a615cde713099a4b4d1ab26b6a322932d1fd6a99c0285b7c44103c7ee80499db0716160382db8fea1b5caea9aeaaf1d940dedf185b903dfc2764bae972005fe0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "9f80b7abc0af99d288971d6a3bd35dc802e7975eeb854bc16dc0d7a2b4c41607fca9d2628d042d65d7a2bdabde5912ab36b293932c12f02f8c97d2623fd4b660a559de3ebfe58ef90f5cb5dd9515f5d21157825803f32aeaf2c1700ff8d084c0b7bcc70ac7f29090ad8c218375624d58f2ce789ab0b345da99ad17a47279d3e8a001f337e5fa7278c26554c673a7bac334efb258471c4156974cd1b54749a49000cc7459fb3983b4258e1f0a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c254b38aaed704cce241ac2308490d469aa01403a44fa44987e3a33a94f51a9b762d02ea54967a8ba351812a6662463e82fcc4ee07af0862113e01c3f6d513b292dd02ae0e3afcc8584b559b930a27f877443d9443afa119db7cee8c632e18f186df0b907e8e3f3267f48a5a44232b720cc9330720e93a31cfbdab14718453cd5eac89beac73c996b6175bae2e91eda40e47625b286d42993fcac967e8320d7cb013a84427df4e30d23abe9cfe"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "abc8df1ef3606c517f5a2d8623e5cd0325504ebfc670da56ca01a570cf3840416b24f21091a68a536e162cc7ed7922869b7ab30283e57f5eeb60b778a06a8c5d6c6d10c91fe5cd37ce47669b785b11a3e1ae824eb9dbc4857a3ecfe0d1aeedeb103688daa07c4e01eb3e12507fcbdf1522fc5a186afbd858cfe8a453278c78285271c303a82417afa3d1893e09b4d2d1146c715cd226292af1ef2ad968ac7e491c127129d106677336767f25597c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "dcb3db73e478b5957276b1c6b16b1cb26d6679d4ce009ee6e5f4dff8ac135296f3ceeb80426d5bffac0324e927d7f77e31d011737e7460cb1f19b8c8aed25a786ebd8b521524c5faddfa009c6799778b48c50075d43db1dbdb891715f038595c597380a8d02baa3f06c58bf3d610148ef84c7bfc610455b63c1a8acc9e834cc079bdc9452bf5a1964c75776f33ce648d71b99a3a97f776938fd76314296a892973f1ac73cfd778eb130bb7aacab4b1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b390160aab430ba9fd39956bc2cffbb81e85f83323840c665cb3c8534dcbba46769ace43b21a0e0f6808b22cbe2e64075c5ee4aeb9f6588cf377a7f10f824aa48e9c3ff93c2ab1ee3bf2149ec9d51783af44d50d49f5801f5e30183b4a18ee99496357201602cfb7cdc6c68ea95bdb858dcef3f4333e304183f139811c853ce1397deadea3875036c72d4e018f8443763e5679bbf177b6d6f9295a9f0079dd6f3d33d523481b44d5abf432fb5cc372dc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "eb380ddac0f8e07171d05759fac1dc71a22448e1453ff2430f82944743457b56a6c5228f6d14408d703cdb89305c5d67efd1c8cc9b0b145e51784edab2beab0021dce4a93fd4113cbdd7c7a33a6a77028228eb6b2a6750dfd2f842769da9692b8f1da6f9ce58ed56a63a666be97912f464ff57d3214889b7d66394b759caa18e57266b63bef3002a8a32d1138ff033e8f078d88b2f01b800e3f1181acb82460b8352b6736b3f122f02f416fd4aa40ec98c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b480f56a66f51cb1385dec327ce586e81d3d9143a7301dce4bc593035080d6c2e1f0cb256bfaa082f68edd8542c0e0166464021dd2a5d721e9f6ce79d05208c348d9c892beecda78200ff67d61a96d7d2df1eb5426d41d5cca0ae3b25e896a914360db473c96899b923fd850a03770923473fded07c0b82cfa2e8c6f9aeca4f266829ebd10a07c1662d2ab1f2986eb9ebc33559958db46a89d46c7dee617caf1740f5e3022458d5cb0b8476381ed6ec8abc7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a91a2d8ecb6485fa780adb7dab9679ac17f2b269306589e1eff3bd7842c2e004795ba3367ab5e6bad584159a6e6c063d64c0338cdb19f89eba55059992b02258f27fbbe1b5b748810a6d00cb365e08a493b98159a169616b8ff88f7e67e7ddb6502e03481a2c601852ed0b6651fb8963af760fdac36edd4fdcce1529b2c3800af3e63ab6bb013864b49950f04a3c784f0e84db4a1f58aa8b4a437b86673d124caa8534afff3fc5be3f5d5f16ba6f862ab5b9e7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "214b8e934e70e2e9ed6ca65e198bca56c84f498fc642b5add5b709b62544171a4d57f82d70d1fbb5cd4359379aadd8dbf274da265ea78302d66f130aa5593ab968a64f596e5f72093ccc3e5473e2de5cb4378823e6282e93d52fbdc4217cb1f942da7e5fdbae8c5ccd44c59fde24128bbcc3436bb6e43fa6d7bacb7a8714f693f1348dedbb024a8c22e164e0e7804a51bfb316569c3fde1b771fb2af9a648d949cd701cbab36a5bcd87c6923428301f3d64f5231"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a2efa708dda81f8ef989f37cb10cf344a8a91548cffbab474041b59c5ebcebf34355e38de9e3b853201b3a4547762b9d10bfbdb52d8230c057fe84177b7790f488204d401fb8dc35fda836cbd549d504c47487ca1ed4fe80f327acd84a43e228c43c811070d74b788a3ea7b39b4a908bf9a17098fe36723f994b0d4cf2390ad430b979a5a48b89e1b7069656f806d53ded32cd256e3d3ad0ec09738b822aba507c16d15f2f4b748fd050b7984654e597d9b30c63eb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1c8e20f3bb00f4d30a8b6d53458ff0b13032bdae6bbf1c99389d32069284dba2e82e068205583bf493de4cbf873ddb0018d866d4543e480b7f3040be117f526e99dc60a60bd84c3ebca8331dc2c126304369420c5c98d00f55d753cf7a993d58962093fae8460f2af2e495908bbe8276c50c11d15dd7ce09dfa35a49bdb6c1b0679f4abc2645034d9b61e07f29af79239dc2a47bd04157ccb5e77011f8dda9f75ed06e1fcb6b87b18a0d02a2648d91f6800ddd041a85"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "34a6209350420b4fe7eb859cbc21e7323f73921819f03529de3051d248f5ba3d73694eac76ef6e3281c51755ca6d625f6f840b181e18ef1f227669603e7f65875891639111d996314dc4b6e5a2dd18a8fa2da32ee3fc1514a44b3b105d291f3d49990d2a1c904921685b9f713c484fa1787608c735bccaeb4ceccd3e799960354444cdce5d9880a3100ca285560d2b9d3842391f60ef89126a58a0af453b1dd0d8ee44d253a99fbcc9f5b0ccd7b80cd2488a57bb4b23d8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "85aecb151295bb3b7c992aa4c2534701e6ec0c9893d9bc8b95716262497411d9592b4f061357e7de7d03b0bf213c0c371c513e12fdf5d0fcaddf8b4b801a261082731dd269ab64e436dbade46152669a945bc51b9fb583cf287c76af8f236b2b44c85649066d2f46ad699435c3c3e954a9e85503537d70f34c06c5aa7c5e0cff11d2cf37c9ce6e883c837d7873a0b152470b26e21c82fb36c300a5e0bb62c81d09444d0967f0e8e8533d69660686f2c7fe2cc6f28478e231"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e7eaea666eda1ad214069ebe8acc8c01e7fcaea21ac8bc4922c686f8f256741f6b29c00fba4e9556b056e3c1ee55d75f06d946d77450c5398e17844ded4f7276693bbe9109bfe9507f42bced1971b3b03f3d70f25f0f99e29d82969bcf1bb663e4c4ae2a04882e05baa2f9c34c029b700c745705581cc5dcef33472cf2319aad0d1d37d92add19e20c88168475463969ca10c5e37cdee346482edbd9133636ad18690070fa7313fa39a7b8656b3e67340d386bd91eb2069262"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e0d3cae4de4bb434d22e779d992c671825b2af5209a236d0d82fe8103d21bf4519401ce2cd4c2852a8aa63f4aa29c8533ac9cb280bf39c92e5813208f563e7b3b88393f611175e99bdf22abc75e2cb175e5bcc9e0f614766e8c9f61b675899e701fad79ddc1f84c18649e50795305667bd22f1bc5dabf914ef207770c99ce3d802ff9afb36c829e5eb34c3218aa3ac47381da335d6dd1757c92af01f51b45d1f7b08a2ce50586474171acaa36927b10752268bc727b057f7c14d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3d237d5b4e1fd29c6677c0fe437bc6624165b3a6e3ca157258dede30d0a59346a57b49ae0cb049324d5c6f289e91de76ffb29395a79e4937a74fdaefa65ea2513a487b8b50675954b72c9d551b0de89ef39c5596e678bae4b5b1cbeebf86e6818555fe886c624fa37f645120973fc92f4f83b263fe6d3e6eb4896d3bbdb8840c01a5f96d9213ac0400cb39e045ebbdbae00179afb31b77823a1ca3fae079d2cc4b09fb0525aec2ea5421e2c4b5ff9e2c881a29b49f9868d15a5dc4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "89a8c115681b38ec1fef2137fe0dc1c76d53d042820536455233743830fb08a9d601fd1f3d796a08472687e457cad3166dd4781fa3ec0ade3ba31ba38a528932f0c6a1727d012631f649e50c76b983873217fc3c397c39ad8661c0f818222c55b35d0679b8ff13ff66235ad5bbb64c2eaa2891e400d443c687ddba429c2ec038c080785510cf999e0d2bf4e3918b27ec209145e0176dd533ff69876e4ac57438ec45ff3574a8e03afddf802e946385a158505526c1f6f6d816db2d3b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f3f8196eeb035d4d2d9a0312f962190af5451a245a04ca77d2f7201c57a34f81332822730fb68bbe9c7876fe5b2dfbd56734110a9cc550419643ad094dda17171fcb06437e69e76c6614f4b90acfa3767093ad1eaeb5ddd3ea30769efdae090545eb74c482559c9c08e78231b0f235402a339eaf72c1c3f202a456d9a954e3187325bdaec1cee4a436e65a7ba401596a88c8b116470cadf8143f1cf0206eb7fea09fbe13178e30c7522218d80cb5146550f94c0f7085011bbea616c82a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "17790b069543cc3116bf84983c6fda6b6782d48bea79f4359f194a0a91f071fc9dff793af64302ff581d60905ca0e0ec07e16bb1af54d30bbe2ad2f522611d6d164ca85274041ab0a041f78a066480f19cb44ef88980febb1108513654b06480350fb68923b64b3c06c749d9da05430a0b4440105c44653e808a8557f59ac721c99aea7edf76b3bb3f1abebb4a7e55babf42cdc03c91135b48ff1554e57a96f07ed9bab8e6f529c93c6ccfa6961a591d8d05d7a9ef4d333b7722d2b29b08"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "86e0a5f8f3e995dae42d2e73d87438cf139f4bc42bdffe97dbf8fc26939f1454a254fdcd56882b3f552fa30b25ca62d0315fa49f1d5d42020b68bfc7bdb20fd4957ed50a533b2be720a8af438e68f174cbfee6817f3f0b3be2bfa365ab51942dab2b321def065c20cda6c7b1a7aa6fc3a1f1d6b4185880491601507ae2a0fa9686b40aaf0524705b760e15d9db822bbca58fba45f6c2d1b33fe49381120dec862d3940a3b145106125e3e99068beda1b073e5f03d38f6973e5969a4bab896e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0459375f9886f055105cb34a109eb44de838bd5e14812f0719a40593aa1bd3c76c8339b7ad90d934d23f1b2aef5aca6f795c4e0132fb1e2d0702fb044f4724fb33e75c5a5d5af60501bec7c873349b1b8bb756ff7a816088ed08fd1a1e79830396d3f73f4656da870a5c1c0aa238c024ed490ec93e0e410c9312b96b5a64313e3d404c6236c655c9d9ad5df6b64e2149bc62f0173f33df60840f44386f4726ce15ee50fbaa0aedf1c384225413889f68571e578412bc6f6897081848fb7df0cc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1cc2c138b0cec83dddb572024356c6a05b456b889f69a8dd2cd8007fe7a23715b567fd6f646c0282b5c5d5360c0e1dc04fa45f69010eaaff42385ba85fd8d343bd1f6d2ac4556fc35ef57394473652f468bc28a0911eeca4234cf0f5e15d5b61a5c8e6340e974835aa8d5e36c63cfafb67bc1388d94e2a57f37056ff1925401f3ea9b497ce7750af79d45a8bbfe9cffd0597a0ad17cbffaa0f90451ac25ed14e807e4ec68e0100373a9d951d094c43308ebc195aae1c68067e1308bbc6144d0464"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "464d0ed294396d610f6b6ce100a876c0ef5b6d3193b746e2152cf437c422a2ab89abca1af36d2d585ddca437e43f9b045c129147820b0ec937d75f5051bb70c528ad9d419a38c9062d665a111e738e44ee9ec9c0f06e566c7267b05f8bb824854d4441c4cd3ba152002c4425e42e9e67926ee8f152f4b76f8bedf50855bfffb23780f02897765571a66b22c3b91fb115ecbd8f6b60b47761cb89d776d8376fd7a7ec9f267e4a27c0339d05812fb98e332100c3540d26a13d1122c09ab64acd30092f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8b18cb31351dc0e00052722e236ab7c57c35c6ffff24a1dc28a56854f48ededdb067315234f6b25308a45c8fd9cd8130ecb24cb5ab0b1719796681fcbb4fd007f92c7d946545a0ac340f7e641cf3647756e08a96d4bd3396b691c9701b149c0c3859fcc922c76d2e827b5717198f015644301acfd2629840a0196e00e9f50477fc561dc69c8df6322d05e922212f2a5d5436701bc3256cad9d868cbd3ca4616938c8cb0e6dca03d2acec7709aa0dbb82b558b912f7e18283fcf13b7e32a992b222f341"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f9962f0b9c6d5628577fcbc108d5708c2ecdc65c157ebabadfc68350cd0734d8ce6277544f39b16438f3b29b8265bfe421eec22f854e7ce8c0d165ddb38ecc523d895704764dfe263303a72db9aec08b1aaf5edba746b585fad6af8b7c61c7753cf9f1fa6121959842bcf74566cbd18bb8471bcf438cc3080feaca7891d326e627de7fb0f104851f1afcd832c3031366406953581540eddb0834dc79964facab1e7b8a45a3c64c6726e3057f82c22c674cc09f41bac59aaf3f7c727f4cee52306f91fe4f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "fd7af4e31d0f692bd74c4089e0af8890a933800b04521d59bb8c24bacc43e96193fea4475bd67419153362b83e54d235d7c7816d4027197e73ec1474dd2d7d003c1297eaf6ea80586d748cc52f1719b73f66cd6174c09c12524f960cd4d4bc26d066020d2ead9af8210bb78eebcb7e480604a1eeb15cb5e3df95f8d701b2d3ebb0b5b06822e157f09fd5a12fbf6af1762ff5cc9a7bb780237fe572eeb9fc4f52ea8b3494b1690fa5809819af4680070d25081dc00bf531710a13d8b661fbeef104ce7bce72"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7a366aa68ea6dd5eaf6820ad9ac4a4863a3e67dd54d1d54b13f4bc88043398a5bac0c735750e5d3d65c157f97cd981aa1ea489f58d6ddd187fd18b8c65bcc3f36e22f564c64786254f319e25bdfd3edfd9d9cb744e7d36115788ca119b892ab4bffba4b467eeb7be8bfaaa09d902143765622351126e1d9707929350701875d512a762d28ec4d9e506d946078be66c6f57b9f5f21d8121bb339e9688b52a7ec1af7c4d0755026a531b73cb940c512d28d3421a302d83acd915ff2fc3c2a45ad5ebd91db7168b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e29fdaf8cae1f2aceb376d53f550170e5d18c44b3083c835340ed6079f312c7d1776824a4604d7967efda33b3824f106abfaf08f359555a24b78dc8ed6787186b8650a19adf64a8a7bd3d8598d9fb4699d3c8df6007086b03141a78aed1fab1ba46d4ad57d6b165dd2a405a6725e50d7bf0114728d1f81eb542abc72ebf499bf795f58f8bd0805b15fabd16de34bc646a7199e2df0e80064a9aef599c9d60884a6503fb9cc50efbc6e46d6dc1c8992978b9cd67b6d61b779c99e98d01c4c84a258a3318e57672a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "da61658788de4b16bc6eae4d9999785448e4055e730629275d1dadd048ac007440ec203f593c4e790cf9bdeba07ac926ab4310c5d6c196fa62dd239b22074bffa535fa1e9616bf7ceed9ecd56b7b6ec1d1d6f88c8672fc4087db0488cb0313642599682a5cbfdb7b79cbff91b4bcf76ad8f779d9fc2fa8fb95c4123775c7c0ffaea19dc28f310aa40cf734b8de7aea233c18b40ce01a851d0e208017e4a8f36c74d344a41dce80b6b180699021bbb13670c2c6025681b730c5f2ea581923f900c9e42baa2d5e0a38"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "31f1b58b087a656bd501b6b89b95e87ec36db90469b3ca02e8bc2b2f2493ccaa9dee81e8877f41a846b717ab550bf3ddc60438d747520f28ce4c30db9ed6be0e5062e5bd4aeeda7c42b94792247b383423cc24dc84613b1c8c892fd3c926267a1f6b290619be07126a34a442b860980967844138155bdf8fc4869115fc65fecd28b16bba47b4651e8bb586c1189dbb7d557124e04943ef79754beffdfe63ef9151610abbfd7b9696b98a6af7f5af088a6ce6ab23ec7533b7ce2942ec61bf9baedab3f3768205ebc85b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "77b6b031b61fa2a66115ba10bdbce0922cc3a5fa29a1410dd5e7021182389319800c3c2e3587af3629ae821366ccd5fa498960786814c3a2263a7b912d2ee866a35a51fdce1df8c7b9c6a86659bd6d2a05d665fb0b6f39582f26d648420abdc1f0404739b0502f85ce9b0d034d51a3eeabc562a8900ecee65dec28e111befc3c62ef8f928e707cdd2ec76068f3d347eeb0f062f6abd23e7c0cbd50061fd462dbf07bec30ef13ced3e5c65d2d8913495680a71a80275c0ca20d7a693a3b25f8c96beb78dabfdc55b8ab57"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5169d680a74be335c0bbfa5f28985595b9d23b4a7101f92d11332ed5938b578900c18b6c2cd6676bf3bdf93121b6537d3aa97b3856b2fda9dabcb1a01f7eec7bdb8239dc8e6aed00f08bce422ad6b834670c847684669c84739ee2e26baca9703db038b92b943bd9ce0f8fb2711e79f8722115e6d7b8eb9b5cbc2b7e318fdae83edf3dc24b400b1907c8b8450c6b31bc975ed4303c9bcaf84b4003077f7c39514244434f7ed1b691ec9bf4c01005e14f40e1818ec376cac21d60f3986dba064f48655a5ca9a9f6045957db"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1681227674cf4bc7124559c7c3409caa4eaec0f895a35693d454e44d79166bd93d29fafb5750c4235609d8a6545b19685a3049466c1e9a01e79221ff4d045390d3493186d116eef714a26e79dbc38a84d2b44b5909524d123ee2a1db6c5fb8d97f6d9776acd3a77210107c2853780225d49b8aebd00486eca5f469d0fb4ede6a7bb72ff92d778357b25053307db21b4f394c120c42712d52fb2ab3f803b276d5d81619a94560bfa0dbbad76a88f6527e453ee3eae3cd0dad3e9be3bbda9277a167d4de833f2a0876f13032a0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1ff6e40a736e0aab47957a8f9631e7b7740645cc10e00ad4f1e95837bd45dc95f44d63139c28351c80432d195e8970222544d7b9f31b29cfde5bda467b814ff381b0af02ddfca8199f18aceb656eaf34df471918b5280c0c5e08ad17dafbbd22627febdf9e62ab1fc0beea3f3732ea166b4715733ec2c68262c09103ee96b062a0d112af1e800cff59b970101626bf3af5ec665d9de91467101e8a261dc733fef971639ada5b8c7679efc6a83cd6195fe5ee933efac81e8f83d0fcc4147f6dd7ddb35c1d0a17395f3309c4328e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b44f695a21679104c2ee1662abff4f654712c990c2b8a89141481e5f33757484a2b921e822c5f37e5703cdbca480df2813163bab64fe5f7ee2958c6e08d2296908cd2aec4b66055f4d64d39967fed56f1074365d1d0e973cf10cb6b8b872ce0debe7658d75d73a4295d858b316559f9ddd9c4adae98143d990880dcac0b8f2c803a6162f0627bc18e8b43c0ccc1475fd03c51b39a64d93e7d60bc63de383598833fc248b5daf8c124ecbe9c39e1be8cd9c0e5c453936b1370177720f2d0b76078088ae483018ae6d5432b4324aa2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "dde2dbfa04b50b16c03e7134981e2a947f49fd05ca06c25d3dce5203bdb17a281af45a291f9e0c3a463468f90415322bb4e61ef0016f47f169e0cdbd4dd88b352f6f27c12ddb9705a1269529eae07f3d34f6ec9fd5ac8eaaf3f20fb1961c188a771163c50778dcf452ea52b17723399d3f8a02967b0e27cfa61ac1c90adb585d6fa504f646e3d08c9abd695405563ea08b0163c6a4f91c88f7b0321e4393e234a355e3fc83a5245ca46cc6236e6f053d854360a7bf379f521acee6b6f54deda7b2fe233208d75ae77db9d1d827afe0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "fd0e4876256b31547b3bc1a2c2def5c417a5d3fb2c3771946784705528611d0bd60b1b5bedf3ee074875b6c8f38f57eac0323d85842dc3980c133f785379ae98fc732cdf733e7600b0cdd895b15ef0a280a01171fc7be87995047e70f8f4a1556e4cd7f0cbe0afd63fea4e51d5b8ee613ce38241e6f6e9e25c577cc8ad7dc914c0f26314a6a629cfd85d364ee73db9e888036fcad410478b563590aa10324d0b7d95618d36210fe8d086808b09fe52939f629d0e083ef6b03c1bc46b7e57779649f5f2b5f9f2e7f99d4017d1c20de6ce"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "6cfb70ed1c1ba261bb608217a062828357f228007518657378d4c633b5f15a54946cdf4a25f3466860339b42ad84b2a13553a8c7e49999716d7c8bace66ba68bb499d7840c622fa93245bf144b0bad70bdc16a27ec9bfafa92a1a58e846c3565d1309a29a371fcc2105e50b769f4fb620d839f042fde48e72a01e5889154f0572e31b7d6caa4326f393cba00100404c38ba6150465cb181aa29d4031d3e2ec3f09310caf4dd0bea9bc5527aa0161e41ddd923c4c0885dfcfc08ff92e85c703901dfe0ab233066a3d2d41a282d77fb767bc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8cd5d164a2b79c9e78b6235e49e62a174b7318c8c5b9a1e56e344951ee03daceab43c7ad9240e6ace541a052a1f500145b4dbd6590bba86f094f0b0696650c64be3ed6d041acd8ea6b0a32082eaf396faadcf0ef1880eb8ac2fbfd82b41f4755f02e970061328a7c8d93de8043d6d8df00e0e3527ae796738be6a76513e12af27807e981d152aa64892a1290c5d28636b4f7917cd0b7c4cb84fc00f4b9687e195cf77a52cc14bc1f5881a02f2a118d2296f7a254ac1a6d566ce4c04e91e42c398ae305e406db300dc3a9d450a390958fc62a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a7a88b8862ac6d4895b627d2b4ca88f144781d69f64da7ff294b92b1f398c8740f94664c8262d5f1a47ec35cb2442fd91f5a0e74317782c2dbd861022945447313d7e5d17f930ab9f7e546dbc02e5df9f07629baf206fac4c3a5bd8de1d4172fb99bd2423b67c9e6c16d5648d3a95554996fd9d8ffb8a0dea44cf70e9f3976c53187e3f54810bafc49ea8164f77e8f0e6b1207445940b82f7a2ba783d9bfa1bd19cf847d7d6a5eb5989433c7aee0356a021b0701ffce133cf2242560b451abfc240d23ab2584de14bc727537a7b5fd582c4322"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a3dead87fa53514025d8523b2ebcd89c7136c51788a8828c4ab62cf129e39a13f0ebaa2c990e621511bb6e8c1f948c4ea4775ae68263f368d7a7d52346a2fb45fb098d7009d46ea17078b8a0ae8a6cdc2368e6df793f5362bda3ec1d88959ae4aaf665d6d755902c908bba2ee95a38f59a5bfa9564103b95789f31f293c8766c51d8f41fb598581dc1503a8904b6efb74071c9fc7f5d6028a609bd4780f283867eb71e5754d1459d70f5393a5da9a2e0f90492bba2264b3db26cb4294082a8d98cabeba6733e5081a8d026f8a490858d8855d239"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "cbed99581a74de8d52bd80a970cec4fad1deba42a37496c7a584dfe75f1f883e1810b2efe18d59b6d92d800d1a0290b956fb35f8f8c9ba0878e0e5d477fa1d4d3fa0d7a835a25cdf9282ad93dafdfd90a28d7314d42b4724cfa495a599eeeb501a719168e38dc82200a593ae3f34920eca33a0224f511247a733f03ffaf1ebf476ad3645c3f1807bfc01619dfaf8731405d5efa106df774c0f3f018f8a545def065578651b01c33dfa5643d4a1b03dc9953bc943ea097f1a616002400e1cc4241084f63acb935561e0d3ba0ed5e487ffb3cded6a5b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "cebc668bc2e2f5089abfd20f1e15167f923a3d8514839eba2d6321f91f4cc7045ccb9d07f77bcb4872e42984e54b78f2e1c5fa66882c34d0369342b78d05443001b6eb97943f80d90689f140343b7b8b7e0eb2b5aa41d270c6cca1062c11b3cc6e0801e62213cece8860aace2a94176702f5e5f3ee31cb09e8c5b18d5d1e99a66492b115f11eb951e78b268e19009ff509605c66cc319d38ac0402c20d384c3ae4772b8d3aa4ad03dd19a639d5dd4a7e88307d68cb7bc13d768f1bfd7724cb2da50812e77a516ed36a1666b23ad3c5d8c80f4a94b9f2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1241811d00320b1ba7c370bfef2e3f423caa0680c22d17674db382617ec02be7c360bfadf95329ddae76919208bfe6f138b45885a5cc54e9fa46096707625c8b6a9b2be1e29428a98177a8c7d703969fc6f34c7b4cbcd316b7515b23502d0eedff8dad2d918b2673089160ce5bfbf1a03fa43bded3b7c006fe23af584a577b6518238706cc1ba6d0f462bc3f62e68948dd2332fe3ab624df9662cde869cdff1b8785a08af7f14aaee0217ca2e29b3e1679acdd25ff50958aaef05c13797bbe4992ba77c3091320c5e1b237decb7131a1eab79dfd636260"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "f175fe4bb81904142c7bbf271a3133444b8fde748d151672c471392458238dfd84cb643715705e2f21d7193ba52e06b81f6644a8733eaf71aaa9b24be772e6491b68717d6755b4af4e45f43cf88f874fca71a01e559d4a5a1f60c321d3e0ea0a8854b8d51ce7f8febebfade0edcf04a74f3c21814061cc7e9e5a48a40c49778a803a0eae7449f73512016f272784e32a4cc78c8d0f30ce3832b7e2d324bdd7b0c3d09ca49c654348760d261c55b56cb7767535ea925af2f0159780bdc8906c57abcc6319f2a5e9f16bf6729bc6e919311e5948aaffd84e89"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "8923aa7bc888549a47f405f61f09cfd235a9126250566506b7576d1c65a7e49dc76553f1921a4d1e1458c35bb032d4d804e421d8e19d9ee0dc80fe4de1f06d183c3ce60709df1726450532ad082275bd2552233bce9b15324dd1211d939c0ade85a16b5618754c6bfbba9dc15e79c2e69d375918a5301d8fca2fc5fb0dd02d71490575192497f769967c5c5a15a3552109d862c5ea21170db9a660418dafc942982d56a46181ec290c6fe9322738f00ccd0dab2bb825e557e39d61662409609b5d06676e9801ee7826076a665dbc9a0b65c17fc48d6223cfda"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "4dd761fd488c41c89c1998a95dcfae35c7907221195c6747aaada8e37b900d6c2f1a69e874a088dc2a7f01c31c64d877fe0a970716446135af511380276a51b067ef6a4ab914765a36444add895d4586a941bd1ba8db907487159a968bb8cf2f4457585b331cdaed2dd327b972c5aa71eca959add6dde4ad0b97f82bee9dd5ce457b0bb3b6da042068f6ff2bb8c20d8459d44172dcfd6139215a42aca13ebeec835357d3e7e01b1a684a4089c1e502cb655331acda4cd2f1c748ec2d86c8841f7606cb13271710ae816d62c3e7e4e35c968cd608f54bad127074"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "af5d2912543cf85417cc243ce775ca86d09a3463a6f6caa554834cab060d88dd18678dc22c46c99529709afa2499e37d58e345a72d40dec191ac1035b9eb6cdb85c41e6d5935574b9600e3b6acbf9292d237b410827e3e4ba40023141e19372bce972e16561983a0a2d06b5659224a8cfb4fdc253311a7c7c3b08d34db27c5118f5423ebc5d0503dba6ffc8653b5d0be4e82e3b0d3cac1f5b2a5efac448849c42d775fb659b041dbc793c0b64a58e884410d6aaf69e3d7bcd7bf500e61ef66f16f77ad1b4c3f009206577196be081b7aee014f4d62cebfb6d058e8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1317d1f48092dff2f78bacd0ca292e7a36839ed52d7bf1e0e729ff800372456a544ece4e740443652b67dc697e316009b3dcecbf9cc471193dfac3935c81f928744770a8b250159f44de9461f068d024927d5d8a4aebd2061fe015927ae67658aa48209aadd2115de5d02fb55fc5b4c274c7c9f2dbe218116fdc235e37d07a9fc5747da978c4fbee1244e5210ddbda05984f0e3dbc5b63359d2c928051a4d5ce6cd3857b6864ad1dec588ceb6306328f925f195a05aacf53d9340427f5fd433289fa0320f073417e68f356e4e6e5fb6b8a2a467af8fd51f4092afc10"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3dc8e8ad7119b010d186e1238acb4bbaf33a2d93852ba91f733611e981df6597a87226f6c6d41edac6981d12e7642daa82c0490fd150b8d57f790630716b188af8285925fc0ff8457b4aa93963adfbdebacb8ba89fc4281bc56e4562a632e030f47093a7a44ec563c23dd6556cdedf899a2d3c1298f63e7959716981f82743b3f4d8cca371c7881550fb30bde8b7feba2e80d0ed5ec63e166d0ef17ddf8db2cf8cfe983d88eb40b0b10e0c9a9eeb38b220f7e013bb952c86b5ad11fc570f525c0125e01302a28ed8b1a97cc79edb87846a8e596c4d28f5018f2387a1ef"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3a2f35477d7056e9ffd101691d80926ae1595c6fc618be62d6b24eed1922c1fdf10dc62d019a29b87440f3f3311dd90d0155e726eb96c3a49d985e9bf719e1480ecb4f4460d16cac29aa64b77e58d9a414160ed5f90ef0811c161c4e860a2ac705c988ac3567b1e63de780781b61ca6e8f16087a89e5304ceb7a68506ffa692194130eb82d1a458cf0057cb4a08346ace3076c72728c4e7abab4bf0add26d33eec3709a7644bc2c7cc355a10887f245f92fc049d733dfd1a87d2b67c9ee12ea4545cb84df60f3d8ffe209b7ac2fb3aada065acd7138d3680e36802f671f0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "b8d04609ff91f7dd457592d183d5482fe26f2862069011c5255d4c265c7906b55c72d2befd472309430fed0664b39cb67d16fcc622ada4976e2ff23c39910f85566473ab6bf0f5a8e1315310a2789ceddda80cae84f48f7a2d6c75df0277b5424d2c54c20209a9cf298f94d43b7769ada163ad1b4c2497e6a2406d41af3cbf11d233b882a46ee9195ad7f511b3de6369fc65e34ed54715681bbf8afefb337fc2f26adadecb429fda383b56a2529cfc7aec86b6377dbc17d03557d61f5410c106672d3a553c36e2a3722663f47d75c4c45595a9e7579d058aea1b35a5caa836"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7d2f8d13222f1aafc0a47689010713d019feede3b1fdfad25def42550cbe918f5f4829787413a3606f854e0d431c6b2761d53f92786e969965d06700a39945adbdc17972b0ce501ddc5c6dd4b489944611ccbb9e62134d98fea9ef5bb6b5f7a684d971bc09e21fe6b40828199864516bc0b00323767a6d9506c8e3aa47c7676feaa60c820d70d0d8bfb7c3b7abb8d47eadf3beca782c79f6cb469c7c8c3340c14896b3489cb950fd23c4439a72f5c8a9589efcefb85bc786ee32124030a1c6d346011ef105cddbb398a6fef999cd19b04fcbbc6427cc385e8399d2611f0f3e65"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "723898ccebd8d2746ce9e3796d429462fe9e34cec7fd6c863e3d90c4f51eced7e241365ff428161a6762544675b6c805e6cc04c625f4d727e6e2e596c501ca5d9baab86c7b4d04fe4da505acd3e2d34b477ece7339b2064ae9f8d991d4f21de6273fb32fdd789df450b3d10e8961a5061290af31ea4f240a512da433fcead2c0908f9a020a0167fa45b7575e65f4febdd63ae251953afc407de50c5ed5da15384a16b5bbd86ff3514cd1f7cf2902cb7192dadd848987929121b051da8de022239c01e8b8f40379c0970e31ba98859bc5349637fcae05c8a50ea7c00fa3300b1af6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "6433591eaca79f1bb52aef15dbaea5e19f2b29312e594ac58702c3f96c297d87ca6ee2bffa2f961f41814c410b763125ec7a412e0621f32fd4b0fe2e17ac262c75780bd43877c56c413c2a6bd8c119ea73baf2cb821d46b7684f81101d3ed0429b6a94ad91742f7bd51b11bf4c12e5547c30e870f8e407bb5ffbdb96b10f901430eaf3e650b69b7802e93ec2c75d05183fb905f21d590e79617e4d08838a1ca126cb47d7e80a55253444682b49543b97146e71a96ddab190821a3007b28b55785442259bb2ade35e0cfe11e994f3894b7f3f5fceb341734e6b52d7e22b22f678f283"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7d7e6bca9da076e7e833dd86bcb7d86fa305755e46e252eb201982fa3a3bd7fbd5d04d9077908a694de27a236361ea68a1f04adc231cc3856de97941ac8936152c51fe55e02be1673f315281cec9ae3b3f10f1668a84c1a96ee3f0a889c22cfeb7d6175b3bc23052971ae2e88b9be6fd5454b6256b8d62bdf617ad6cb22ad93fa02a20f1a3c29cee6447dff6806bcc7169f0b94b016c6a68f0b5a581a58d41a0cab986d4660cedcf1cc6113df7423bd486ef4e4b8f7fe33f3d82afccb9c86eea598156aedc90bdbccb75fd55c254975a193e1a6937e7a5ea0aefa94de7a9667565826c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "ac91aa5434938c1293186c76b7ae28a8437e621ac79c93b4b8145de9951f1948fd4cac58b781c3593a444b51751297db4dfd6bca2aa3452156f6bc412010da90270b09b6c5d0e4d8d836dadc2bdc35152ec6fdec7d1faedcb33868c81717100dca6bea92e08408ca6d1bbc7b68e7f71ae48f2fb2117697b9fdc4b46cccdf2d81bd6dc8c76c225ac71a49548b2f3788e47d56127a703e6705b08f4949738415b3392a701025775359ac29940a12161e554e50e6a6d5c0229039b25326ae1a61c93035f68c389ab2e88e8dce09b2f55d9291e6e640d411a543f5e97fa50eee2c10aedaf5fb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0502fd59c54a710da5a6c4060d3573de3ad4c9a5a570722f95ccef846e5c7a574c8bb772816a6e7dd5c56331af86139fe7fbf3d7a3fb1c44c9d8579094c622b061060ef1f3dcd77163932ee6fe66af70d854e31ecfc086979416cf3e193c5dcd19ec1a9933aac0c04964981e4f76169a937bf9a454d9ca413d6a1097dee798a5f60a20b0643acb48c7514f026a9219a8d7aee6087584ac33a8c52af289699e604f2659bd7fbb45342cc3be9e7fe7564fa0332e5c2233daa9472f59d59f38369c1d153cee628fd5ba511c0f5df6deface8ad81a2864d403273175db913047b80556da8ef76e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3d7eb388c75f23aa5a413c1aea1fcb3ba1d76123bc1834b5b973b385aa12eb0041ab55f571c7432287786ddae33539ca8777250f7f1960c973d7afbf561c274104f3c4cc033cab139247e43d98a5c814076482fe829e2eee0e302b3265676fdc82cf921935d19161ae814394cda9818f8e7c2bb4c56565790fffefa9ba86ab35816daa9bcb35f11e72637e7a6b127d084057906035914ff0ae08f71b601d755371171d249c7f87586e53cef8e175fb6312e8771ff5ead9a2f68050b8bf1add0aea7a3cd2f6d18eea57c6a7a13918cf7ebeb21113d09ce9c7a641a34b8ac22c9c164e9729ffda"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "a2a3b9a7c7f17ed8c51f8437c92e8918d0102f320227682192479a094a0cd6401d689a24f0f1f0ec7ccd7d5343391a7ef7590db47c83da0b43c133c673a679b49dacce5386a92c50880ff2803489a1ab8edf6212a155dfe8e3cdeff98cb145ddc1a9a1260385ffafb175e954d67eb5816c4da009df5a21b96a5cba8e77050220abd4f0b21f87fd44bf0354b9ea270cb8e428bd2d1822c8ae57256a413016b67894a3c38ef74bda287c2ee7aac2efbf244bea3dd1e9e94b0e1a5b18295f538253d7362e0bf0b5c090ca4b537e692bdb993e1225b23565cb281372f4d3a0ce247b96b1af1db06da1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "42563ee8adfb99ea56890d94074a727c47d0aef5756145a92b1a41ae4cfeaa91351c88668afc1c4a715f35d265a36ffc103c5820098b13f0bb4ec0b761066aeb521a3b40dd40bacb45f58908cb2742e453c671754e85cfc43b12ac385dc1c72883260196658ae345ca16d16a08548fc8197709e3d026b7130bf6a0a74375dbfe619e64d35c2577f8e2d53eca56ae60d11551208ce41f95d19f789d909b4d9fcb52d289ed8862d5f1bfe5542b9fc68dd49d20f673b53a06e2ad6d74725e1180228a2dabf7f2089f4a974a22e91f3f413c2a4c18d7f5da95152a0338c3f406b71d760a9b2c32a5328b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d699a03480c769b114056f7b8c1c2f4ea6013f785216a91ab9cf02df1a72625162c74979bfdbb938f76e0248f7a18c20dca243104aacefd8e8d2c9eba6ac0123f38fdc6ed1dadab1ed473097276f6c1a50d1d6fab39f2dfa8e78cc965d5875f73c703785c77e346c4ca551aa4ac0c25fd74567555209aa5bc7b7455a71f3d6cdc94a81297d1aa2901d2be9423f91e4d2c6977355910b0bb0dfad851e87bfca350e1406225dec1c514cfd2325411425c71dbfcacd4e21338980af552e5f833e4743a96130e71988005a26ecaeeac6abca9fe7aab0c8ff320ff135195532bde17cc5cc398476fa935597"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "cbc516abdb43cc8aa4d6e8c0dd28c4f8a1286d510a9ee9db7815814b635c6f17dbee4d4d18442d03faa7b21bc9756e7ab276f855d8780e2c35e0fdc4e6e1d2c7bdba75b3236e761289731461427e89ae9c36b815727740004c7aa565b9c8e52796a67a5be792c01365e6005d7bdaf71eeb26cbb67a2c503e978cc1469cf90cc5c67a0277eb78227c66c999fda6d87f6e2c8d69ca614f5335fb77333ad53bd64a904b9a588e99d5362b965fa509829bc88c5163f09ba40147e61834dab7316ff537eedbd207ab34c103c014048a33861e85d1e5384145a002b0d21cc0dcbb55ef3ebe8c595dcb910ec06e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "e63a2558d900ebbee7ecf56cfd1a20b7ed133c1476f9504fa814917cbd3057c729e38284cacdf8c6e65387be06664ffbd5dba5523186a05231c377b26ad453b8ffba3439d44ea10e4a12ab7038da3c734927d88769a00ec15d39dd2d7d2ac8bef814d06bcbf804889ef46d2ed58bfe2d0bc4a9bf10594f3c4244f7460f40eb1df72d80ef4c20ea18c2af5a059ade20d634e3d0f184a6d025ce40759ddb4de99746cbe4afe858893c2a5c2f51c19e47e2b2a36c7efef85ac7bbe63657253a2ebc0915f09f288d7c2030f24c923dfd0f29b66f8ebfdf3cfbb6f551ba7d83c068ba8144e566df37b64edbf111"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c12bc01a2001cca9b3b2f8aa048c86a70fb6f9c0f03ee16e0dff7154d5989c0f22e0d5db0c05a0a24f85e89e1bf1c1c49a7343158fa5827a3031fad7a3b62cf69a24f9d6541984c498b758a70a91576c0209be7f83ff47b3788de21ccb1f2c5dde0d654b92ce5d6c4da20f0904a453d2dbb2f84b43ff4d8ae84432d896bf07ee1047af1a942c5e2576917a279846451e1ab36843ff1c7fee1202e1ff7d4fb14788bf4ddd7bdc050048d73d2375440575b04c83127a5a4189b926f777f24d9abe76270c4f1c64a3f9da133180a8f6f88f423134ecf7b49df41c57b6c736bcb2c896f754dbc07f6b4fc807f047"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "df5437f55443ccd7676aa471db84da24295903a87f02dd95e23aa468f6fd60be2e96d1d4366e2bb831c0f396b9b92328baaf45641a1c19339b768021f6aa823c953fefc56e7dd15805f4a3c0f83f9a21b5b3a7f81591febc8f0b1614813cc4b09df3a67dc11cb3d0f4d0399101da60b7986c1b7af3d19afa3a245a6c457332caa28e4c87be2596b6662992631d6ea8361c44f7b5f0ae991951b18fa187de224966fd074e2add2cd4e442ab18118737b75751c32b5c14afaf06318c2011b08e62c5c2bb35e48ab946a59be2ba6dadb3dc0e94b69ed0de84f02d6b5c61ffd4bc42cbf393b51760583477946b5417"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "491baaaf3061239f157fb02dda59e76f995c710d6170179821aa556264dd237e2eeb3c1b51efb94623e5603a0f0d3f0cce2b5ace1108e5c1f1edb359f5e1d65de4c99f1f48cca747adc58786515b955f65177e36f4f5c7fc696146fd4e7f7b9cb38c81b215fe08b6f034710cf9076b29dad70f8795bdb89bf4688c020fd2362feb2b6876fbdc9cf0edd250709cc7d059870f080ba9899f977007110274ebac11373578d11c93b1ff5872253aa395b1bd0b4e9da71c273dfd8c98c46d65d692c688adbd656bbcadd4ad4a5531e5ea96728afd25da0ea6c3b472bd5924aaff2b0e26ec6e0f0496f1cdd8c3c8cb34b4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "39d12b605a6241a2a239a9cfa9936556b2d6f8d961b6165425f0a9b8c63940db014e41ad93aa9a94e68517a7553a4979a58eb24238eef54baea5384836be636b9953893114830467065ecb7b5d8b53fe8d861001f3c16e616cc5cbe0650942b40b068dc6740ba40684f837685f0581d699ec34226967223228bb73f5449d933c2fd7577b4f5feb3c98c5cc890c86e56e666b135c9ac46ca67f0ef5f4f5516beb221006b38bb37c1e8df9b549a5ba128fe168b83752c588d0ff9ddf725d31431f0ae085b5dd9f251124e0d575f9f79b6d2c28ba8d5c79a7d106c2617f77863a95fce0ff9962e6a21c4c00647ba27e8f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c0ef3d806481d316ab62e4a75d42256f653138e6bfbfb42c398e88ee6f3d5ee2e85c5f6d407196718cda38c890111f2ca41a20238fe78903b1071591916b964e1cb15e1c357cd0f2c30aebf03a3662e14cbc1cd5bd4d58cf0054960edb9f091e3f4ed6c11a87fcdfdd84b8f5ca3ad8f1e4e5f06efd9594e1c5e5240ac9a8a373e140850d6b4eb4df20da33ce768b46a645fd73ec0fde5b799d548a12be5452492e46dba3da03fccce0cd3666ce88373ed07633024f29dc883c16245f8992b0b134e5461ad8def4922b4911e526f3e3c72b121fec0318369d1a40a4fadfa5078585494d3926b7ec6fc522fc85e3eec7aa"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d9c78eb756dc9335fed007b1156ee7d89356291bd1808b025b7ac3bdaac8597c2a68f3d99890c04578ada7fad23125dc1fdee6e183f17c332882979913fd2610352e61f4f1b6e3c711907f4c6b5ecdd5ab5659b9788ab98e734455ba2a564215c7cd147a838e4ee02f4f35fdeb38706e64d23b8dfe3a58b0e1fa76c63038dc6aae308896bf52b9f851b76f3760f808b00e17442328f78287161dc283affb00862dd500140b86ca787bd58fd20f8425d2b5735467f723db7a0f9d1d08800ec395078a5470670056013ee95d7e59eee510fa23fe899a59594a3f823a1d62017a03bf464135c6b71f405e774c25563218b45f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "fb4504919408017da6d8a0622f6e95b9ed6b14db7841bd44f046eef98bf26bc834ecb3f4b7628ba3a53fc4d75075a25bfdc8536d014a185c0087fca9e011efe29e794b0e02719b617470fff50b02562a521e6c109a6cc183d74ab70b60ff1326255f8de02307cae03ff1bf49a771b5b311daa67cd4131004b2a1efaccd1d157c468637ea400caa891a531261b32f0fc61b241ff0fb8d1e8cf90703f80945a50dbf8edd650d43dd1c19dd4bcfdc202812f7dd5fe7d66dd39b3a9be37ecbfe1d0b3609c79cb19e49cafac783f02b9815912619cb3ccf8aaa47362bbb98f21fb48f61dfbe20d48b281fac8263b84b7c472b1719"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "0c317a9baf295d07e050e015a73f8827778973d924707c71aac3edf5e0f13bb51ad4f391b56d9b6dc204bbfc71d091d274fbb21e38317b7844a5e1cd58225279471401512a874ce6da69b364e2bad389069892057eba683f55b97ca4d18febda810eced644b6475fb00b64a4d86e81ba344a41b8b120eb63b047a45c9251c396448b07afa0315e062f40c430cae51448eaed7946cc58c7ff05917b564e6e4f99d1522be04d235659b3dc1e3f7ab0e65a5bb1cc3d26c1589e8532cb361f6bf2ef0b6b27e57569030db4e543a684c17db3dfc6bdb984834d842fd3642412ff88453d78dbd0cfd3b0771b6c0fb7ee38261cd1d103"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "3edf5d417a0d1a38a3a57e5265eb8df1542e25d90cd64292ab63713cb4a75b9c94850978fe2bd2f96f160b61c51d62eb2092d19b70c4448c2900f57f861f34425e2af6fd060c94f237fd4cccb745bee2bc47d0b4d5d8a57bd612af6109f5bb9a964bb4a6360371bec3bf851c269aad8d0f191eb7a9b77a1e98bc096a9b5586252526508a1c25cc1f6b0c1333f97c86a8799ad2ef8aa123400aa8741fe6277a2b92a6c4d4edaadc7fcae7953fbdf5ef1b499f871f66809b0d72c9b03e9b49cd5b8a91dc1c749fada0d9a4595d085f4b3e7643e239689c87a73455b31c9c5a6e0b899934408edc0dab175ce25c659c238b69e9ebbe"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "80f4ea817d4649de3f998374e11c9b41c3db5a5ac86b53031abab352171f46b893592ac5650a5218f7e54964b962b297079751c1d82bfbb9333fac445067a04473d26cf96a71c0c8092a8571d91f4413bfef40807206584d04928a5243f5ecec60e42aa3e0c83a28c8df71075d9a52ffd3519d5e40a0b20d4f4d1b21c695a249b5509a540cc0360d2906d6fddb6fa327beb9b087b1ad4d40ad435a2d589406369e7655dbd6d4f6ea340e35301b4bc02ac03ca85e55c8e50dff67718a9c1aedb6a0334bdc99f6c8b4dd52996bfd8dfca840c10aa92f6cf9d84c3994f5719fbc8d6a9592ffdada572d32aef67a9d7f9c897e08a197cb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "32d5d8e29fe1b68a49a30ef65787bf1ce003534891700ca2efe6dc4ce4b8770857130cad694451c148ed276c6843cc92597b3cd0129a043cd2d0803d82dda405b4151c5b4121bc276540c1723f4ce0f490d5ba0a4bff3a92117898d6ec362cbabd34052b6f82633f059a9f7948b46ab11cbe78d23f5a4828dfff412acf333a21cca6aacf570348a12a44c2939d360099234c2a68aff2bfc7e6fa0af55fbecbcb7d5d581a133bc46af75437147bdc42bd2cc0769098953655b2a41b5fec382ce63820ff4a447c7f62c825e68628279b8da57071a0c0a187943cfe5ac99e8447c3cb565973642115bf78cab6884cacd202d5c086ff09a5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2042c8097d147442b9e0787ad9efc72f0b351bcbc3e4037556a82e29a6f0015f0c8e162513c6529fcb5e1e790c789c0022e34ec0541128ee07f3597062ca54e4bc9ac6566861d0d3e93212c229a318b379f03e480776b0c57dc0053535d3c94ebe54887fd89212bc3f1814eccbe00918049f17b9db59aea0e5cba970ec6bae937cd059d0eacd3bffaa19dc1ebf5f5cd90681effe1eff986754b80c5817589abf4ca404bce5ccaa066c4753494b8620e0a796f7892c6ee8c62a44a9b40f7a098d344f09b45134583f860a3b59621aa31b96e094713406674a9974df69d206eb8efaffc9b7feda8ae5757406e5278873d68114e11dc57ee3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "32217f6690884421cd6b0cb94884511ed0047b3a317cd2ff899b2dc866ece06ae9473018019124ab7f91918b9410a67f411ec349182c7f9dea73fad0152716d8f9910496e83fd50c56c93d5b088548071d65eae370722ac87178e62126f3a702ad323bac48bd7c238d65033c76187baabd952d6008723ecc4f492ca401b85fade1b219b31c6ede814e0ae35e1977ffd7b3b8c3b3474a3465a860ea7f27ee4b92852b09e4343772272deb760621bac6beb48143013dbe1c7ff1b7ff51cf66d02bd59e76604ad8b3374c688a05843f8af5e66465d5b5b738712e809d87587468c970e6239594720f1200b084fdd0829f282912a86d1c6360dd"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "da4830165af94ace9b01248bf2c373911096ebd83129cf6f454128c41efce7fea91156ab30a74a521e533b3ddb21b4a48de883c0ba7028420a128b3ead0a0d15d42766ce4d0607413cce1f947fa25f4a5ba29573554e68e005978d3b14ac0216ada50e02964759625bb313926f5ee7369fb3d4f50ab7e20244d2930d7f0113a44dd4b3edee72c15d57224d24b9fe9d9c1df2ef77f7566b18b6fcbb1f550f7717d4cf50b6666da197bfe2f4c3c23fe7d1ff6f770711dcc54ef9231bfa6ff455f859fb4be342e33a623723f17976f7b014ff8f50bfd2a687622dccf55e724d17337c65a1ef721c9ae68aece4906387078f058809e2a139e9755d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "1017ee9edb76500379a9eb1011cdfdb02cba68c53e47dcb2a2e29075c1b275f6a60782257b69e23e92563e8e6ef00bd99b5eac94ec223279e18422af405b0708689e8e3ae56044286c80f4de21474b4d0f8a5e525ed4394dd285abc592fd52410893a4f5b695ec9015bb3e95d9b8905ee6357122020729177042dd1b1a36f3fb33f992d4abbee44753c0d911745a186db1dfbfada785e5cfdb119217107f51e33858ddc5cfc97bafb75d838baf5e7407176db5080dfd3f4417221f0d1673db79289f16cac33e1be977f75a699658a965bb4730118202eabe34486f35664eec62610260014755d2704339ce611e759eae087b9b9ffe237bb8e88d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "5fb36a00c95a45ec6e7526c2a35189980e231aa36c2c8d6a9ccf288abe52ff56f77f01a0303d267fbed331bde0c30555e0f5a298b148fece91d802e3c33c233b535e37caef41ce16ab5df52070fdc3bcff00aefc15a552b0760769011e81aa70fb5740a3a568a15b5f72bc70564630209c5356dee3e49b1e089d97d9137f33653d81758f0a38aa53913753b5703249b5bcdd0104c86998d815951d5d0ec94685dbcf98e7fdbeae10361e9522fe87ba60c49fa6290503016cfd85a478a91fc70e9d43075b167058f1589b194296c750c6693f9dc245e9b21a483975ada00217166189b99b6c56357886e9a8063da35e9d20db24002ed36edbbd1981"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "c6942d36146678f56f4f8cec34f97448aeaeab84424cc0ea957292227287ffa954d5186f3e133eddd0d460c741dfc104f0015165e5efb7ee33043b97e55b2995b7887c49a5df5546459f445819a55edee3fc39743352b5b197095787710dd55b11f88d1155b051b97918885916a59dc8d64dc31c2e1eebd3995bf144c86849b992a35be67fa30bbba18a565b1aae21ad2738236014cbde288ecfcb910a7edbf33d41bf59fd81675a8fc0f3dce569d0662d6d565e8bcccb8528328aa75bc98e09163be1ff113af4d7e4dd514ead744117754e54b4bc85c194c2f074d3d27033011c66beb70e984ef7b675b70a9eb73a3dc0c3d626bba33a87cde4dd68"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "55f5d8c7a4fb578ae7f02e9d627b5ec4e6279aaeb1791be6a030e00f3f715eff486affd8539d41fd7517588cb6a08dc77c0a3d2ad6be11492cc950d44137c3209177ca50e5a48073a85ecac000fee2cc97c81ea4c82f9ddaec4ec479219ceffa06052d6c0fd57044b7c69c07b6c361269aa6e2fd6dec3d93cef32cd8a698af82f676f211053787b69194552d03d3db7ddc508efb6336f17541d7c18b18e93854d24644ca999495e8b321303b8dfe9eb430d251ee9e265dc838b1d201f84018fb89cca117954c7c059927e5eb8ab54afe5caf720292edfc003959fd93f945dabae0c3d56edd430894c103f579a048ba0c097a17fb4954df8acaa46026b5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "7c4634ca8c596c5f3003986ef7a26bbae711327cc9ea020b546755ebe23e4997731fc1a9760de9e44529a8d671f99f567db7aba5d8b594ae35a61bb585cd2a950f6c216f3173614b1f96ac120085f79106732e7bb55edbfe1b525c23e4dbb117a23517ac4a89cd3d09a39b421ac1bf3f83ea763c272f3f653c3c3807130d6a998394359bc5f51047c46f77af1c738b363cd243c10d1c7a0d9db55d8e14c91bced77065d0a5c21061d181ef6fab35218aeb065b49630b9e3b29ca1d64fe21ddd2b906d9a9421eca770639084e97ca4b04cbd522a355f7e30465219a5ac869003f5c5d40401d5e049a9737081effda31c6ea9e6aaf4205c85470d5a2ca2d37"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "2953a3385e63c8bc7bf7f441756deedf21f7620528ded82100ee0639658c6b973ee4878b1ed8c7a9a755b2aefaf010855c64933c578cdf5d9b93f991ea044ff020662757901c1b6014f354f061f7274c2d912eae78601944be200be650a869219a9f67890f5d6a48ab799c3676b24009642690105b20e034c6632b36956e43d049b306b0a596d0ecc0e79cb1ff11824ea31e8d023bba757bf2597bc58e83076e1c45b2f0e9b72fa93f4423589401efa4916d2f9a82833f44e2866bcbb222ddec1b5a70ac5f78716e4389e566dff0e9c1b510eae7a3106c47eb60b8b5bab89acbe46c055a7554bb1cdfa485a891c3b548c7a5f1d38c03cdfefa5ead65031ea4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "",
+            "d4a23a17b657fa3ddc2df61eefce362f048b9dd156809062997ab9d5b1fb26b8542b1a638f517fcbad72a6fb23de0754db7bb488b75c12ac826dcced9806d7873e6b31922097ef7b42506275ccc54caf86918f9d1c6cdb9bad2bacf123c0380b2e5dc3e98de83a159ee9e10a8444832c371e5b72039b31c38621261aa04d8271598b17dba0d28c20d1858d879038485ab069bdb58733b5495f934889658ae81b7536bcf601cfcc572060863c1ff2202d2ea84c800482dbe777335002204b7c1f70133e4d8a6b7516c66bb433ad31030a7a9a9a6b9ea69890aa40662d908a5acfe8328802595f0284c51a000ce274a985823de9ee74250063a879a3787fca23a6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5196"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ad6bad"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d8e4b32f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8eb89056f3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "410497c2ed72"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f0de771b375c90"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8662db8685033611"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9ef9f1eed88a3f52ca"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "08225082df0d2b0a815e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0f6e84a17439f1bc97c299"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "895ec39c78d3556cefdbfabc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2b396b3fa90ab556079a79b44d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "abae26501c4c1d6123c0f2289111"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "bca098df9099b3f785a37ba40fce5f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "19b827f054b67a120f11efb0d690be70"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b88d32a338fd60b58570fda228a121113b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3f30143af1cad33f9b794576e078cc79062e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ffddb58d9aa8d38086fcdae07e6653e8f31dfc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "abb99c2e74a74556919040ca0cd857c95ec985e9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "71f13f89af55ba936f8a7188ee93d2e8fb0cf2a720"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "99734fdf0eef4838a7515426f4c59b800854e2fcdc1c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "579b1652aa1f5779d2b0e61868af856855020bdd44d7a7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1383d4ab4a6d8672b4075d421a159f69380ff47e4bb518d5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d3fa1412712dbbab71d4c6265dc1585c8dcc73380cf807f76a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1d57868a71e7245667780455d9aaa9e0683baf08fbaf946091c2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ef80418fe7049c6251ed7960a6b0e9def0da2749781994b24593a0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ef91cb81e4bfb50231e89475e251e2ef2fde59357551cd227588b63f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d7f398a5d21c3139cff0562a84f154b6953c7bc18a5f4b60491c196b6d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0a2abc6d38f30aef253579a4088c5b9aec64391f37d576eb06a300c193a5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "02dd758fa23113a14fd94830e50e0f6b86faec4e551e808b0ca8d00fef2a15"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a4fe2bd0f96a215fa7164ae1a405f4030a586c12b0c29806a099d7d7fdd8dd72"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "7dce710a20f42ab687ec6ea83b53faaa418229ce0d5a2ff2a5e66defb0b65c03c9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0320c40b5eea641d0bc25420b7545ac1d796b61563728a4dc451207f1addeedcf860"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "460539415f2baeb626fad748dee0eb3e9f27221661160e13edf39d1b5d476ee0672400"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "02de8ffa5b9c748164f99ed9d678b02e53f4ae88fb26c6d94a8cefc328725a692eae78c2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "348a61a0136436136910262ad67ef20644b32c15456d5fad6b1679386d0bea87cc1a2e2b5e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "24c32966c803434d48d2283482ee8f404f598cf7a17961748125d2ed1da987039b1ce00f2ba7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "bd07cb16121d3b47adf03b96c41c947beadc01e40548e0d0773e61780d48d33a0e2a675ca681a6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a35844e34c20b4b9371b6c52fac412afe5d80a4c1e40aa3a0e5a729dc3d41c2c3719d096f616f0ba"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "6df1efbb4567747fe98d218935612f8835852dde2ce3dec767792d7f1d876cdae0056fef085245449d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "48d6094af78bd38d8f4b39c54279b80ef617bc6ad21def0b2c62113b656c5d6a55aea2e3fde94a254b92"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "cd6e684759d2f19083164712c2aca0038442efb5b646594396b1fccdbd21203290f44cfdecca0373b3801b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "155dfbf26103c8354362663677fa27d0e1ce3487a821a2a7171014c1bd5dd071f4974df272b1374765b8f2e1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "15b11067f311efa4ee813dbca48d690dc92780656bc4d4c56510523190a240180867c829a8b8b9844175a8aa23"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9bc27953a17fb84d5eabe95b4ea6bc03ea450274abccfb6f3938ded8560fb59662459a11a86b0e0f32fbea6bb1f8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "03b78fb0b34fb8662accdf350a6be75ace9789653ee4375d351e871f6a98ac5e782ca4b4a717665d25e49a5ae25d81"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "687e9a6fda6e2ce0e40e4d30fef38c31e3513d2892bbe85c991fc3715947e42bc49bcd079a40ed061c2c3665efe555ab"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f3886027d2049a8909e26545bd202d6a6fa2a6f815d31c7d520f705a81fa606dd695369c37aee4fa77dc645e9b05813ceb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e4a412ccd20b97797d91ccc286904fcd17c5afe8bed0618f1af333c052c473cd327637d951c32e4af047106036a3bc8c1c45"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "92f4b8c240a28b6238bc2eabadaf2ff3c4bfe0e6c61268ace6aebdeb0691450caea4287db8b329bde96af8cdb8a0fe2f57ef2d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e506834b3445e1a9a9b7bae844e91e0834512a06c0dc75fa4604e3b903c4e23616f2e0c78b5cc496660b4a13064bb1138edef4ff"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "27031955a40d8dbd1591f26e3c26e367a3c68f8204a396c6a4ba34b89672896d11276966a42bd516716f35ed63e442e116dbcf35da"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "646b1635c68d2328dddd5ac26eb9877c24c28390a45753a65044c3136ae2fe4fb40d09bf555271646d3dceb1ab1b7c8d8e421f553f94"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f6171f8d833743bdee7cc8f8b29c38614e1d2d8d6a5fff68bec2c0f4dd463d7941ff5c368e2683d8f1dc97119bde2b73ca412718bc8cb1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "45db1c478b040aa2e23fb4427017079810775c62abe737e82ec0ef8dcd0fc51f521f29fe6412fff7eac9beb7bcf75f483f3f8b971e42454b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "500dab14687db3ca3dde9304af5f54194b37bdf475628af46b07bfbf6bc2b64ecef284b17f9d1d9be41794699bc0e76c2878b3a55730f7142d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "31bba2efc7b3f415c3f031d4c06bb590ae40085ad157370af30238e03e25a359c9e133212ed34b7a006f839173b577e7015a87fdff2270fafddb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0600b3fb4b5e1ed0c8b2698ac1d9905e67e027390764821f963ad8d2b33cbc378b9c25c3ee422992d22b760222ed5697be0576d73938ae9d634ed7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4c0ca4f177d132594a4c613bad68da24c564efa3b4da0d0a903f26534a2e09f8d799d10e78f48ccdb0203954a36c5cf1bf24c076632c2b022b041200"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "97aacf2e1b013677b2e14084f097cb1e64d7b3fa36f097e189d86dc4a263bcc46817cd1ee6ff0c7ccd9acef63201cdc0e36254e19204a7388643bb571f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "71fd6846ce7adb0843d6063546a16b79b54ad6c0f018a479a45817624fa221f63525084860559d1a0679c8d89a80701c62743ec2da8419d503f8f0cd7946"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f73dfb046def3362d6de36077dae2cee2587fe95fe0800548bb7d99737897096ba59052e0dadcc1fb0ccb5535391875328637a0376a43a4d89366758dfe3e2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ec470d0aa932c78c5bcf86203ec0014314114765fa679c3daef214f883a17e1b4ca12f44433772a6e4ef685c904b2fc35586c6bd88f325b965968b06d808d73f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "cf601753ffa09fe48a8a84c37769991e96290e200bbaf1910c57760f989bd0c72e6128e294528ee861ad7eee70d589de3cf4a0c35f7197e1925a64d0133628d87d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f15413f7d6fc54bb55829f698da92ee42fcf58dde1aa1bd07d438ecdc32ad6bf2bcdbecc99f18ed43e81b33065af5a4ca29960ae50553e610c0bbf4153d580e73dbb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "84b1738adb9757fb9402ef7113581291136184d7ae35fe0b6a738da6acb0889d4d5bac7a957024e3709fa80c77d3859871ed1aa25cf488e438a2d24cfadce6008761dd"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e02814bb81f250c1835a05108396b74c7878e737654bb83155e241774d04e639bbc571b413cd9349092f926c8a149a53cd33e9b63f370b6d460e504199d2e7d849db6cbe"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "aeee4a789956ec0913592c30ce4f9c544894da77ba447c84df3be2c869100e4df8f7e316445d844b31c3209abcc912f647735fd4a7136c2f35c6fda5b2e6708f5ca951b2b0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8cfd11ca385de3c843de84c830d59278fe79b70fb5ddbfbfc1ddefeb22c329ef2f607d1d1abbd1cd0d0cc7c5d3ed922add76aadca0d2f57b66cb16c582b6f18f60aee2f7509b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "852e5ce2047d8d8b42b4c7e4987b95d23e8026a202d4567951bbbd23111e389fe33a736318546a914d2bddedfbf53846036ad9e35f29318b1f96e33eba08f071d6dc665149feb6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f225c23164979d0d13874a90ee291627e4f61a672a5578506fd3d65a12cb48a182f78350dc24c637b2f3950dc4882a5c1d5d5bad551c6f3e0093aa87e962bea51566af3791d52d65"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5f33864d882455f8ef046aed64e2d1691e5c1555e333b0852750592e6f00d3b5ec941d0c00e99629612795d5870cf93c984b45e4464ba072a34903b400a42824ac13da28c7c1cb1959"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "7baaee7c3eb68c18c5ae1d45ba381803de34e36a52e2d7ccc9d48a297273c4d8644b473195bc23005f7a4f5ca790b1fa11f6a96e585e635513f11745dd97a69c1222204ab28d3c7735df"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d0a2a3fc450ef9af7ae982041feb2842901026467d87839c33b4a9e081ea63d5be60ae99ca6e42393ded45255b8f42886f87ba0310572d9f0d8b5a07ff4b6bae1f30559a844983cc568560"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3aa4164462b3e7044c35b08b047b924790f6d5c520b1df4305b5d41f4717e81f0cd4bccb9a5a6594773832b8707443adde4047caaed2293f92234df257df54ed275a9658fab483d0576d33a9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c8b4239fd7f1b893d978268f77f6505b5775d89090374322d40083b0f4c437423f670ca213f7fe05c61069725da2561646eefaea597ac48e293fbad44c2872046857e56d04a426a84008cefd71"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f94839a7024c0a16971271b6727c081770110c957b1f2e03be03d2200b565cf8240f2873b0426042aaea996a1784fadb2b27f23bc1a521b4f7320dfbed86cd38d75141365ba9b443defc0a3b4078"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8af934fdc8b3376ca09bdd89f9057ed38b656bff96a8f8a3038d456a265689ca32036670cb01469cc6e958cc4a46f1e80d700ae56659828a65c0456b8e55f28f255bc86ce48e44377bf1f9970b617d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ada572989e42f0e38c1f7c22b46bb52a84df8f7b3b773c9f17a5823e59a9725248d703efb4cb011abc9474e8e711666ed3cfa60db48480a8160615dfabad761bc0eb843d2e46299c59b61a15b4422fdf"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b11f1ea52a7e4bd2a5cf1e234b7c9eb909fb45860080f0a6bdb5517a37b5b7cd90f3a9e2297f995e96c293189b807a7bf6e7633bebbc36674544db5f18dd33020aeaf50ee832efe4d3d053873fd31ce3b9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e54b006cd96c43d19787c1ab1e08ea0f8922bdb7142e748212e7912a1f2c0a4fad1b9f5209c30960b8b83ef4960e929b155a8a48c8fb7ce4326915950cede6b98a96b6f1ecb12715b713985dacd1c1180413"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ee2c2f31a414ccd8f6a790f55e09155fd50aac2a878f9014f6c6035cae9186f90cdef0b7adf3e207c3d24ddfba8cd321b2e9228b02a1182b6973da6698071fce8cc0a23a7bf0d5aefd21ab1b8dc7818549bba3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "6d6810793bad6c7efe8fd56cac04a0fb8717a44c09cbfaebce196a80ac318c79ca5c2db54fee8191ee2d305b690a92bd9e2c947a3c29342a93ac05796484638787a184e4525e82aeb9afa2f9480caebb91014c51"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "91e4694366cff84854872667fd168d2d42eca9070cdc92fca9936e8361e7266931f418450d098a42686241d08024dd72f0024d22ba644bd414245e78608942321ff61860ba1245f83c88592dc7995c49c0c53aa8a9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "608aa620a5cf145f4477694407ccd8faa3182465b29ae98d96a42f7409434c21e4671bcae079f6871a09d8f2965e4926a9b08277d32f9dd6a474e3a9fb232f27fc4235df9c02abf67f7e540ca9ddc270ee91b23a5b57"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c14f75e92f75f4356ab01c8792af13383e7fef2ffb3064de55e8da0a50511fea364ccd8140134872adccad197228319260a7b77b67a39677a0dcdcadfb750333ac8e032121e278bdcdbed5e452dae0416011186d9ebf29"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "03fcb9f6e1f058091b11351e775184ff2cd1f31ee846c6ea8efd49dd344f4af473f92eb44eba8a019776f77bb24e294aa9f962b39feecf7c59d46f1a606f89b1e81c2715ac9aa252e9ce941d091ffb99bb52404961794cf8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "11e189b1d90fcfe8111c79c5351d826f5ec15a602af3b71d50bc7ed813f36c9a682520984ae911669d3c3036223a53176794c7e17929efab2b1c5b500f24f8c83d3db5d1029c5714c6fd34eb800a913985c218071677b9885c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "69f8f5db3ab0321a708ab2f4234645dade6bfda495851dbe7257f2b72e3e8378b9fa8120bc836b737a675271e519b4712d2b56b359e0f2234ba7552dd4828b939e0542e729878ac1f81b6ce14cb573e76af3a6aa227f95b2350e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "be734d78fae92cacb009cc400e023086bc3a3a10e8ca7cb4d553ea85314f51383660b8508e8477af60baf7e07c04cc9e094690ae12c73e5f089763201b4b48d664b94b4f5820bd1540f4a84100fdf8fce7f6466aa5d5c34fcbab45"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d61b77032403f9b6ea5ad2b760eb0157545e37f1712ec44d7926ccf130e8fc0fe8e9b15570a6214c3899a074811486182b250dc97ebdd3b61403614d935cd0a61c0899f31b0e49b81c8a9a4fe8409822c470aacfde229d965dd62f51"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c31bd548e36d5fae95ed8fa6e807642711c897f0fcc3b0d00bd317ed2bca73412064618c6a84a61c71bce3e963333b0266a5656571dcc4ba8a8c9d84af4bdb445c34a7aef445b15d77698e0b13c436c928cc7fa7acd5f68867e8132993"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9903b8adab803d085b634bfae2e109dd247a7d6249f203403216d9f7410c36142df8fa56fb4d6f78136eef5817bad5ea3608439bb19336628c37d42db16ab2df8018b773baedafb77278a50926370b48bd81710203c7abc7b4043f9a1751"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4dadaf0d6a96022c8ce40d48f460526d9956da33260e1770315ead420da75b122c762762aa3ddc1aef9070ff2298b2304cf90443318b17183b60778f3859b141053e5827decfff27ff106a48cfdb0371d0ef614fc7400e860b676df3176d1a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "314dda800f2f494ca9c9678f178940d2284cb29c51cb01ca2019a9bede0cdc50f8ecf2a77e238b884867e78e691461a66100b38f374c4ccac80309641533a3217eca7e6b9a9af01c026201f0afaec5a61629a59eb530c3cb81934b0cb5b45eae"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4658b7500951f75c84e4509d74047ca621009835c0152f03c9f96ca73beb29608c44390ba4473323e621284be872bdb72175628780113e470036265d11dfcb284ac04604e667f1e4c1d357a411d3100d4d9f84a14a6fabd1e3f4de0ac81af50179"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "491f877592837e7912f16b73ee1fb06f4633d854a5723e156978f48ec48fbd8b5e863c24d838ff95fa865155d07e5513df42c8bb7706f8e3806b705866475c0ac04bbe5aa4b91b7dc373e82153483b1b03304a1a791b058926c1becd069509cbf46e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "231034720c719ab31f7c146a702a971f5943b70086b80a2a3eb928fa9380b7a1ad8773bfd0739142d2ad6e19819765ca54f92db5f16c1df5fa4b445c266215a92527bd4ef50ed277b9a21aee3fb7a8128c14ce084f53eac878a7a660b7c011eb1a33c5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3366860c77804fe0b4f368b02bb5b0d150821d957e3ba37842da9fc8d336e9d702c8446ecafbd19d79b868702f32405853bc17695873a7306e0ce4573cd9ac0b7fc7dd35534d7635198d152a1802f7d8d6a4bb07600fcdaacfaa1c3f40a09bc02e974c99"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ccbbbe621f910a95835f5f8d74b21e13f8a4b03f72f91f37b5c7e995aa3cd5539508d5e234e77a4668a42c239b2d13ef0e55ecf85142055e3f8a7e46320e21324a6b88e6c823ac04b485125c2aa59b61476481208f92ea4dd330cb18777c1cf0df7cd07893"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "87faf0e49e7e5ab66ee3147921f8817867fe637d4ab694c33ee8009c759e7d707f44c69c1b9754e2b4f8f47b25f51cd01de7273f548f4952e8efc4d9044c6ea72d1d5857e0ffeb3f44b0c88cb67683401cfb2f1d17f0ca5696641bef28d7579f68d9d066d968"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "38c876a007ec727c92e2503990c4d9407cea2271026aee88cd7b16c4396f00cc4b760576adf2d683713a3f6063cc13ecd7e4f3b6148ad914ca89f34d1375aa4c8e2033f1315153189507bfd116b07fc4bc14f751bbbb0e752f621153ae8df4d68491a22430b309"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "87d636a33dbd9ad81ecd6f3569e418bf8a972f97c5644787b99c361195231a72455a121dd7b3254d6ff80101a0a1e2b1eb1ca4866bd23063fe007310c88c4a2ab3b49f14755cd0ee0e5ffa2fd0d2c0ea41d89e67a27a8f6c94b134ba8d361491b3c20bacac3d226b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b021af793badbb857f9a353e320450c44c1030fce3885e6b271bcc02e6af65fdc5be4dc483ff44bd5d539ed1e7eb7efe3001252e92a87df8227ace601047e101c871d29302b3cb6c6f4639078afc81c4c0f4c2e04688612ecf3f7be1d58ea92894a5dab49b949f2089"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c5c1f2fbf2c8504a686b615278fc6221858d401b7fe790b75fb6bca6885cdd128e9142bf925471ee126f9e62d984de1c30c9c677eff5fdbd5eb0fa4ef3bff6a831056cea20fd61cf44d56ffc5bda0e8472ecdc67946d63c40db4ba882bc4dfa16d8ddac600570b9b6bf3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "88f8cc0daeaeaea7ab0520a311dff91b1fd9a7a3ec778c333422c9f3eb0bc183acc80dfefb17a5ac5f95c490693c45666ec69234919b83244003191bad837aa2a237daeb427e07b9e7aa6ca94b1db03d54ee8f4fe8d0802cb14a6599005eb6326eefe5008d9098d40aa851"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2eb6b1a58e7fe39ff915ac84c2f21a22432c4f0d260380a3f993310af048b11647f95d23adf8a746500833ee4e467fb52ea9f1039519fa58bcb0f1d0151558147b3c92b83730aba0e20eeeea2b75f3ff3ad79f2f8a46cbbadb114a52e32f018342aeeaf827e03ad6d583bbce"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3ba7dcd16a98be1df6b904457709b906cbf8d39516ef107006c0bf363db79f91aaae033466624d30858e61c2c368599963e49f22446e4473aa0df06e9c734e183a941510d540536377072334910e9cef56bc66c12df310ecd4b9dc14207439c1da0ac08bdd9be9f2c840df207e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a34a7926324ea96867dac6f0dba51d753268e497b1c4f272918c7eb0e34120be65b7b5ba044d583141ec3ea16fcedae6197116b16562fb0706a89dc8efd3ba173ccd0fd7d84d480e0a3dda3b580c326aa1caca623879b0fb91e7d173998889da704eda6495023b5ad4c9ad406298"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5ef97d80b90d5c716322d9ba645a0e1b7a403968258a7d43d310320f60f96235f50e9f22cac0ad239636521fa0607d2f471051b505b371d88778c46fe6787d47a91a5bec4e3900fe6ed22918226fc9fbb3f70ee733c369420612b76b5f55988d757c891d7005d17ee55783fe506202"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "140d2c08dae0553f6a49585fd5c217796279152b2e100ebde6812d6e5f6b862b2a3a484aed4d6226197e511be2d7f05f55a916e32534ddcb81bdcf499c3f44f526eb515cc3b6fa4c4039ad251253241f541558bba7413ca29318a414179048a054104e433c674ca2d4b3a4c181878727"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "29fdfc1e859b001ee104d107216b5299a792d26b2418e823e0381fa390380d654e4a0a0720ba5ff59b2ff22d8c4e013284f980911dcfec7f0dca2f89867f311ced1ac8a14d669ef1114504a5b7626f67b22ecd86469800f1575543b72ab1d4c5c10ee08f06159a4a3e1ae09937f12aa173"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "52dfb643832a598a10786a430fc484d6370a05356ee61c80a101dbbcfac75847fba78e27e537cc4eb918eb5ab40b968d0fb23506fee2ad37e12fb7534fb55a9e50902b69ceb78d51db449cbe2d1fc0a8c0022d8a82e2182b0a059035e5f6c4f4cc90278518e178becfbea814f317f9e7c051"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d32f69c6a8ee00ca83b82eaf82e312fbb00d9b2f6202412a1ffc6890b4509bbbeda4c4a90e8f7bca37e7fd82bd23307e2342d27aa10039a83da55e84ce273822740510e4ec239d73c52b0cbc245ad523af961994f19db225212bf4cc160f68a84760233952a8e09f2c963be9bb1d71ca4bb265"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d1e603a46aa49ee1a9ded63918f80feca5fc22fb45f659fd837ff79be5ad7faf0bbd9c4ba91628ee293b478a7e6a7bd433fa265c20e5941b9ea7edc906055ce9799cbb06d0b33ae7ed7f4b918cc082c3d4a1ac317a4acec175a73cc3eeb7cb97d96d24133a29c19375c57f3a4105519846dd14d4"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b45ac88fac2e8d8f5a4a90930cd7523730733369af9e39bf1ffb833c01108952198301f4619f04b9c399fef04c214bad3358999967c474b67a7c06457a1d61f9466489ed5c0c64c6cdc83027386d6263491d18e81ae8d68ca4e396a71207adaaa60997d0dca867065e68852e6dba9669b62dc7672b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d5f2893edd67f8a4b5245a616039ffe459d50e3d103ad4675102028f2c497ea69bf52fa62cd9e84f30ae2ea40449302932bbb0a5e426a054f166fdbe92c744314cc0a0aa58bbc3a8739f7e099961219ec208a8d01c1ae8a2a2b06534bf822aaa00ca96218e430f0389c69c7f3fd195e128c38d484ff6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "37279a76e79f33f8b52f29358841db9ec2e03cc86d09a335f5a35c0a31a1db3e9c4eb7b1d1b978332f47f8c3e5409d4e443e1d15342a316f442e3bfa151f6a0d216df2443d80cbcf12c101c51f2946d81161583218584640f4f9c10de3bb3f4772bd3a0f4a365f444777456b913592719818afb26472b6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a46d252a0addf504ad2541e7d992cbed58a22ea5679980fb0df072d37540a77dd0a1448bdb7f172da7da19d6e4180a29356ecb2a8b5199b59a24e7028bb4521f3281313d2c00da9e1d284972ab6527066e9d508d68094c6aa03537226ef19c28d47f91dddebfcc796ec4221642ddf9de5b80b3b90c22d9e7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "060c18d8b57b5e6572dee194c69e265c2743a48d4185a802eaa8d4dbd4c66c9ff725c93667f1fb816418f18c5f9be55e38b7718a9250bc06284bd834c7bd6dfcd11a97c14779ac539629bcd6e15b5fca3466d14fe60d8671af0fb8b080218703bc1c21563b8f640fde0304a3f4aeb9ec0482f880b5be0daa74"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8f2f42bc01acca20d36054ec81272da60580a9a5414697e0bdb4e44a4ab18b8e690c8056d32f6eaaf9ee08f3448f1f23b9844cf33fb4a93cba5e8157b00b2179d18b6aa7215ae4e9dc9ad52484ad4bfb3688fc80565ddb246dd6db8f0937e01b0d2f2e2a64ad87e03c2a4ad74af5ab97976379445b96404f1d71"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ccb9e524051cca0578aa1cb437116a01c400338f371f9e57525214ad5143b9c3416897eae8e584ce79347297071f67041f921cbc381c2be0b310b8004d039c7cc08cb8ff30ef83c3db413f3fb9c799e31cd930f64da1592ec980cc19830b2a448594cb12a61fc7a229e9c59fe1d66179772865894afd068f0942e5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3eb5dc42172022ab7d0bc465a3c725b2d82ee8d9844b396913ceb8a885323dbbbf9ef4ed549724cc96d451ea1d1d44a8175a75f2a7d44bb8bfc2c2dffed00db0328cfde52bf9171f4025770abbe59b3aefd8151c480bafa09f613955fd571e5d8c0d4936c670d182cf119c068d420ded12af694d63cd5aef2f4f6f71"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "20ea77e58e41337ad63f149ed962a8210b6efa3747fe9bea317c4b48f9641f7145b7906ed020a7ae7d2ee59435392edc32aee7eff978a661375af723fbd440dd84e4a152f2e6ef66f4ab1046b22c77ac52717de721dfe39aa8ba8cd5da27baca00cc1fffe12c52382f0ee83ad1418f4c6a122effaf7471e1e125d7e7ba"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "95c662b835171fa23f948c3c3ed27bab9b3c367bbfe267fe65f8037a35b50cd7fc6030bfce4000425ef646c34793f0762635ae70487a0216ef7428da622be895d1b6040423246511c2370d6876a5c5d2df8bbd48fb14f787b632ad2c1f5a927fdf36bc493c1c8606accfa52de33258669f7d2d73c9c81119591c8ea2b0ef"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f708a230675d83299cc43167a771602d52fa37cbc068ef9128ef60d186e5d98efb8c98798da619d2011bf4673214f4a4c82e4b11156f6292f6e676d5b84dc1b81e7cc811b0d37310ac58da1bfcb339f6ba689d80dd876b82d131e03f450c6c9f15c3a3b3d4db43c273c94ed1d1bd6d369c4d30256ff80ea626bda56a6b94ea"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f8417766ce86b275f2b7fec49da832ab9bf9cb6fdfe1b916979ae5b69176d7e0293f8d34cb55cf2b4264a8d671370cb595c419c1a3ce5b8afa642208481333522005fbe48cdc700e47b29254b79f685e1e91e7e34121784f53bd6a7d9fb6369571bba992c54316a54e309bbc2d488e9f4233d51d72a0dd8845772377f2c0feb9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3479e04efa2318afc441931a7d0134abc2f04227239fa5a6ae40f25189da1f1f313732026631969d3761aea0c478528b129808955be429136eeff003779dd0b8757e3b802bdff0f5f957e19278eabad72764aa74d469231e935f4c80040462ab56094e4a69a82346b3aeb075e73a8e30318e46fdaec0a42f17ccf5b592fb800613"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "03df0e061fa2ae63b42f94a1ba387661760deaab3ec8ffabcaff20eeed8d0717d8d09a0eafd9bde04e97b9501ac0c6f4255331f787d16054873f0673a3b42ce23b75a3b38c1ebcc04306d086c57a79d6095d8ce78e082a66c9efca7c2650c1046c6e0bbce0b2cba27c3824333e50e046e2a7703d3328ab3b82c9d6a51bc99b9516ff"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "76b488b801932932beefffdd8c19cf5b4632306e69e37e6a837e9a20c8e073bcadd5640549faa4972ebd7ee55cb2425b74cb041a52dd401b1a531beb6dfb23c4cfe74bc84f034156c8f55050ca93236eb73c4e2595d9fbf93dc49e1ec9a31705359732dda73f737ec4274e5c82626dc4ec929e5e2c7a2f5f5fb666181922bd8be575e3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ff17f6ef13abc0426b03d309dc6e8eeb822300f7b87eff4f9c44140a424098fd2aef860e5646066d22f5e8ed1e82a459c9b9ad7b9d5978c29718e17bff4eeefd1a80ba48108b551e62cd8be919e29edea8fbd5a96dfc97d01058d226105cfcdec0fba5d70769039c77be10bd182bd67f431e4b48b3345f534f08a4beb49628515d3e0b67"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "95b9d7b5b88431445ec80df511d4d106db2da75a2ba201484f90699157e5954d31a19f34d8f11524c1dabd88b9c3adcdba0520b2bdc8485def670409d1cd3707ff5f3e9dffe1bca56a23f254bf24770e2e636755f215814c8e897a062fd84c9f3f3fd62d16c6672a2578db26f65851b2c9f50e0f42685733a12dd9828cee198eb7c835b066"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "010e2192db21f3d49f96ba542b9977588025d823fc941c1c02d982eae87fb58c200b70b88d41bbe8ab0b0e8d6e0f14f7da03fde25e10148887d698289d2f686fa1408501422e1250af6b63e8bb30aac23dcdec4bba9c517361dff6dff5e6c6d9adcf42e1606e451b0004de10d90f0aed30dd853a7143e9e3f9256a1e638793713013ebee79d5"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "02aaf6b569e8e5b703ff5f28ccb6b89bf879b7311ea7f1a25edd372db62de8e000219afc1ad67e7909cc2f7c714c6fc63ba341062cebf24780980899950afc35cef38086ee88991e3002ae17c07fd8a16a49a8a90fc5540be0956dff95390c3d37629949de99920d93096eb35cf0427f75a6561cf68326e129dbeffb8772bfdce245d320f922ae"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "70752b3f18713e2f533246a2a46e38a83cc36dfccec07c1030b5204cba4432700735a8cee538b078d281a2d0262110381c5815a112bb84404f55af91652bd17502dd75e4910e062943d8a736ae3eecdfdd8e3f83e0a5e2ddeeff0ccbdadaddc95391310fc657a59724f7e6560c37dc1d5bb5db40170190f04a274c864ade9687c0f6a2a48283177a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "01f3c1333b44077c518cc594d0fb90c37651fb7b2442e71fc0a5611097f1cf7bcfaf11c8e0ac1b1cab54afba15bb9332df6bc64d8032368e3f686c8324b0114e0979dad78a5ccd3fff88bbe89eef89c4be586ca092addef552ed33224e85d8c2f4fba85ac7735f34b6aa5ae5299154f861a9fb83046b0e8fca4db32c1343e02676f283975f43c086cf"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "509283ebc99ff8d87902fa00e2d2a6fa239e335fb840dbd0fdbab6ed2d95e8275402523f7ce9a2fabd4b6c9b533288fbe914bde84365a204711d0977a7d698f4614385984dd4c137e4820035dd6737da364edff1bb62283e87a8c7ae8637314fe9b5777ec4ec21276dafedb2ad5ee1aa0ac99e34a6c01c055c8a239fd28681607f65143082cd4553c529"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c17e417e876db4e123c631f7136b8a85bfd6ce66a69180d0cd5ecfd6f037bb1c7bd7908d51f2c485bf9e92c0e1799ee5f6ab834ee481f5eb1a8020205adb4d0f90126d4e7c2c859c5a5f644bdfa9c649ff4f168e834de6f9769429732099d46d0af506ab86c6fd92175159bbc05c75db8e1fa867e6030d64250008d64c857c47caec3dc8b2ffb384d0193e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "950988fbe9d62a66f5f2c492bc8dc944a78eb3796ec37ba94b6a81a9d402ccad03cd8497fff74c5f4a03081c5fecec48574fecb21c1de261332c23108195d3f6a96ff8e433a1a30eda53dd5bb414973334f8cde5510ff759f7c17046cbb5acd8e8c4a6eecf2a9121ec3fc4b22c4daa72678194ce809024cd45c4ebb9ccdb6f854205cdb624f0787480d8034d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "552a212c403b473741da8e9c7b916d5e5e9bcc9949021ae1ca1ed46b7d4a98addbb604d9fff56175b7e0367db26c9635fa7813653dc8d610befdd09ec41e99b192a716106f4299eec8b940863e5a59cf26cdc2cd0c3017f9b4f215812bed15f69e77edf672178e13c55580982f01fcc2fa131ec3d736a55d56504c545f4be50fee83f1263e4d3f3c877cc6242c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b00c4283dd3d9cd26e44bd97cede6c771cb14f2571b51cfdaae4309560ffd165da025a1bbd31096c3aa8286e2d6dcc3e681b8d01f2c5064ea26dfd0b5156b7a7f5d1e046c5bd1628f8fdae24b03bdf7cf7366900cc013a8cbed9d7f5937c914b08f8c27683b956e1279812d04288515333fc6aba3684dde2292951f0610649d90fe61606630fc6a4cd383649252c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f6e79457bb6d0884dd223be2cf5ae412a1ed425f1e4012f75951b096aea3b9f3581f9013bcae1aff2d3fc1e5c7e06f24af6d53c2c5c238b71c71cc670b05a7ee5204400026a5c4e5ddec3ad96771e49fae4b0f75ec58049ad9d972e5749a32d90f847f1ed2a1bab83db181e541cf5c8adb6b29ecc64dc25add491d408d3eb3ddcb013de7f5ffb6de9dd7ff300a5fc6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "fe1d71e1d5efa3f712d23216ee8ee9139e66bd648b83efc02cdb4d45a28cf36759ff190a84d14d9471477abefb5aea4111110336143dd80cf81e02f268120cc07d746538f968e9876bff8358d390f5b8e7eafa61ecd236cedaf276bd61865fdd3424988201dcdeda2e3e0c33c9e3b3670125dd1049106cc6df5695fb2dca443233ff440f265bbff055483bac1e859b83"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4c80163562872a965dedd8725652906156ada6e9d999027d96f49289edb92f9ef043e9d7c3377e091b27f85275499454af32317535997fb4aaeaf93565ad481ff7d45d2abddd4df4b60f71a6923ec30496c6ae534dc5427107ab4c5e656a322c7ab058d4c13ec0ebafa76576560697ac98f84aa4a554f98ec87134c0d7dca9184cf70412a324aac91823c0aca02537d197"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "fdd58c5ffe88665beb7073c8f4c22472f4bc9390cdd27a42622ca55978b000ab7579f795d4de0dfcaf521b8268980ef1d20277b07567985c0fd5030784ad6c32541ac24e99ab706105a2255fc32935c0fce6fdad9bb224d94ae4eae2a3ff08836618a3adf193630647bce1952b69da4de360f59da303519278bfd39b733cf66820a5e9e971b702f45998b69a0889f4bec8ec"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ff38b15aba3794e2c81d88003e045ac6cbfc9f4833cdf896cefd8ac0c88674727ad9a9fcb9ef36574deea480e6f6e8691c8390ad73b8ea0eb3665c914b0d886546948e67d7987eea248b5feb52346ffdd965d5c835144c3bc63daf325e74b11267e32e58a914ae4521a668839d9445fececa49c5fba41f9e171698bbc7c6c97fa163a377a96456958d6e1d74f91ada56a30df8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f048c19328d60b4e59ed76940415b2c84c23883198bba5699efb0a1774ad5da6d15390c7b55d77d66f37448fe08107f42a5336408d5322f4b630e3275865fc66dccab39f6e13fabc133e5a441fe352d81c7cd9a25f145a6e2e2417d3b0bbc79eafcd7ad688c02011fd268dd44ac3f4f87b37a84a46fd9e9975962fba92c9a3486deb0c45f6a2e044df4bb79f0feeea432c5008b0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1b3e5fe6f113cce28a6f8d6f7809d3cec398cabffe9ff2ff10a7fec29a4ee4b54186063fd5307a2be393c9ecd75a37620bdb94c9c18da69b658579676ec90351d10dc33a7cb3b75798b1234f9f684d4a73a0fab2df3d5d6fdb1c1b1514d0935c1f2dd21486f91c2595b2f8f8a500ff443b9305270fb6f3da7961d9316d4ed6a135a31c4a3611d40e6585bbb34f498cd5b9a5d92676"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "740db337baa12b16897f17a85fa5685acc85e48338867f8ac9c0198dd650f5dfa7c17725c1262c72207e365c8aa45ffaab6470a0e5afefbfc3bb702a9766064f28cc8b796878dfdd3ca9d0216c14941438fc541fb5be0a13d29a996c5c985db4f630df067a5626db5dcd8df3a2bff17dc446e46e4079b8815da4318cb228c7722684e2a795a0ca56f500ea51951a6a385385d886f678"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1465f2d578d167faa017fe8f763ce3cc8dc1e8371d774ed2a8803f12585296ee71a1f2253dd16b717a81f91f0f3641018a0111182b4e65d884b0a3d0292631ad807cdccc88bdeecb476e76f72b5246a630aff6e2401fa9570f85acb73ccb4e19ef04a932a03d7b7985dbe1e5bb410df517fe362321469e6f8b0e0cef6c31d7aa8ec06aa220620d66cc0e133fdee963589b12320fc9678e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "80c051952fa6f3ef6af0f1759ec3e83c8eb91abee1de360bfa09e74b05af2475a0dbf8f9135aa25892919bbe0515898cfb6f88abc9e1891f2b2180bb97370f578973d55c13c35edb22ed80647c2a7e2884d1ccb2dc2f92d7b6ec5843ade13a608a31190ce965bde97161c4d4af1d91ca9962053f9aa51865bdf04fc23fa35a6fc3c8e888941263a26ed66c2dd0b29b2325dfbd1227c5091c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9c1e2a1aed6406052eed12b4495365f2f80e9c9645473f3549b607f20910bcd16dc3a4b173ac8d128129cdb7c76ebbc8e9a2a1ba0d822c66b367e790a69ac71f0a60ed4bff0e979148e3f3ee6607c76dbc572ee5ff17c27e4b52adebb4bedddff517f591a1977299c7cb01106f1453b098d29848ba3751c816215bb0d090c50f9e445b41b2c49d4eec83b92ce6c269ce835fd279e7cbbb5e47"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "466abda8944d0329d2975c0f2e2afc901f117887af301881f63b714f49a2f692fa63a8871fc0b301fe8573dc9b2689880cd8969e5072c57671e0633b041481dab25e65c9de404af033a11a8070c8ab70ca6d465318501afdd9940c7efbe1bb6d49581c222fad251dba4ee0a98efe22a3c4f74da05844523b30bbad6b080ac8df70a02da80bc9d477dfb869adb211e209a316d5dd1fd89a6b8f8e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0e89a873e07799ba9372fc95d483193bd91a1ee6cc186374b51c8e4d1f40dd3d30e08f7feecfffbea5395d480ee588a294b96304b04f1ee7bbf6200cc8876395d1db3ac813e1019bb68d27204e514fe4a61ad2cbd1782dca0e38b5538c5390bca626c5895b745cfca5dac636fd4f37fed9014ab46ae1156c7789bbcbb956ff7ee5ce9effa560731d26783dc6ae8bddd53a5d28133614d0ddeddd9c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "fdde2b80bc7a577ef0a6c03e59512bd5b62c265d860b75416ef0ce374d544cbb4e3a5dbd31e3b43e82975090c28bc77d1bdec907aeceb5d1c8b71375b6d631b84a46153f5f1d195bfcb2af6f597a9cdc83782c5bbbb58c5188a87ebf375eee5212fa52523820a83106e8ecd52bedd60d95cd646159774389c07e1adcaa6b6f649408f33399ec6e507d61659696b3dd249996892d5986b654d94ff337"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f5d7d66929afcdff04de30e83f248e69e89604daea782e1d82d8032e91a95c1d6fb2f5578f79b51be4397e4cd7cbc608ce143fdddbc6fb6c43ffdd394a7df0124353b919aeeac025f3eb11ff246c3b9657c1a947fc534ce48e18feffada8797037c6bc7e2d9a9e2e019fe65627b3feb28e446473e3bd413047a2587f0be6a103403cb3c33fdc212dca14d8e386aa511c22308e632f5f9528dbabaf2deb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "332990a8dba55f977bc814436cf386ebbf10cb487a5f6ce83e13741bac670c6810284fbbe4e303547ef411e964fae82854e8c13cf56979b89ecfedd337aad78260060122d13dfbbf8497acb2066ed89e30a1d5c11008bd4d145b5ec353956310536304d8b8bba0793baec6d8f3ff49718a56e6694f8122078265cf5731d9ba61292c1219a1affb3679576d4998290aba3684a205c3469d40761a5c4e96b2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "efbdff285027610f03182009c89b953f19721cfcdb8accd74bab6ec4bdf3f555ab902cb0dd91284269d140638aaabd211748aa4da3b18cddc653b57e461b9ad8491807c535c08fe97d89eb587c6af19ca152e72479626ab764e8b62da89fefc8354c75a44851f985746d78715a5a92798dac1a4222be27897b3f0aa63d596aa7378545f49b259aa8518c3def8a2ec8f7aa956c43668c8717052035a7c36b47"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0eea9bb83bdc324fd21b03669aa922fbebc448e7d25e210294c07862cfa6e061731dfb67b4810633f4dbe2130d90fa1c65843af436e74219d213c4458dcac1c48ec4541fc6e3b7918ab2bc621aedda53658050900c3865ca57cd5dfa1d28576827401956d2dd8b861fa90ab11bb0b544ded9bd3d62e3278ed484e17db8f2d5dc5ea4d19a0e15134ba6986714c2b22c59c2f0e517b74eb92ce40d2f5b89e6d79f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "25da9f90d2d3f81b420ea5b03be69df8ccf05f91cc46d9ace62c7f56ead9de4af576fbeee747b906aad69e59104523fe03e1a0a4d5d902352df18d18dc8225855c46fefeec9bd09c508c916995ed4161ee633f6e6291cb16e8cac7edcce213417d34a2c1edea84a0e613278b1e853e25fb4d66ff4c7ee4584e7f9b681c319c874d43502534e8c16a57b1ae7cc0723783807738a55b661e617ee285bdb8b845607f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a76b6f81372df09322098868d469fb3fb9beafc5edb32c674974ca7032966aaca5b5c9bffef87bfe626bd8e33d1c5f054f7d5acd3b91ff95324d1ae39eb905b9f2694fe5cb03486cee86d2f661a751b0e6c716a61d1d405494c2d4e32bf803803dc02dba2c06eecf6f97fb1f6c5fd10cfc4215c06d627c46b6a16da0854e4c7c873d50aa1bd396b35961b5fa31ac962575230c07c369f8fbc1ff2256b47383a3df2a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f9db613812f2259972d91b1598ffb166031b339913925ee385f03b3b35dc4b2f1ae78a3c3d99c6ff6a07be129ce1f4b8d994d24988d7fbd31f20535d36ab6bd0592cfb4f8c1ed9244c7fa8a3c46e91272a1a40c6cfcf261c5658476c59793bf1a3775086e41a0492f88a31e2d9d1ce75cf1c6b4b928b3545d838d1de6b61b735d921bcf72e4e0615e9ff969ef76b4b947026cb016e2660ba39b0c4c953369a52c210de"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e601c7e75f80b10a2d15b06c521618ddc1836fe9b024458385c53cbfcedd79f3b4239598cd7b9f72c42dec0b29dda9d4fa842173558ed16c2c0969f7117157317b57266990855b9acbf510e76310ebe4b96c0de47d7f6b00bb88d06fad2c2f01610b9a686079f3ed84613ba477922502bc2305681cd8dd465e70e357534503b7cbc68070ad16d9c51de96ccf0aae1599299331c5655b801fd1dd48dddf6902d0e9579f0c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ee5ff4ca16d1bde59ffaf2d064eac9141c1d8f120ea2bda942b7956ba3effc5f1e725a3b40b0b9223a14d7a50df1681d14ca0e0eda7bb09c428fa3b2701f83a7a3e139485a118f6287d266dbc7fe68c87b35becabc7782537c79cb8165bdc40cc103d7b6d4b627fafa0e4113f92341ab90ceab594bfae20dadbfafd401684584598941f1ffb8e23dc8a04ecd15376cda6d849fe0dfd177538c62413622d172d9d46e05c450"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1daca80db6ed9cb162ae24aae07c02f4126f07cd09ecee8e798fa1bc25c26c644333b63731b4ebc3f287f2318a820c32a3a55fc976576bc936f7384e2553d2891e3771ff24dd4c7f0256906460a8f12d30ed2b23583a0259cb00a9065a757d654d6e4603e7c7eb4a8426b527ae8a849d9350e9094b890367df3e8b23ad2df4d7dcce416bd8ea3badd037f53f7b07c02e5926515f196d62aeb9b8b14c863f067fc12c5dfc90db"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "27ff4e58a34ff1fcd66855d014ea17889a3cf0021a9fea3fabfd5b270ae770f40b5439e00c0d26bd9766f6fb0b4f23c5fcc195edf6d04bf708e5b0bced4f5c256e5ae47cc5651e51cd9fe9dc5d101439b9bc5cc24f76a8e8847c72686e2af1ce7098ad7bc104dad00c096a6d48b6453322e9cd6773fb91fb1eabd05dc5185a9aea07a2f64c6fea9897681b4428aaffe1fe5fd3e8ceb890b12169ec9d51eaabf0ca3d5ba415770d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "75e2fb56327983b04f640717be8cba6fef3655b4d8e5539587d6478356ec397efaed818b8425d052778eb30ef0dee656c52c2aeab079ed496ae4441a365f2130432c87ba757e25b4511656ad15e2eff84d342331fd2814d1f1d11af65d98a424c115ba183437c0d0aa55f5c44b8685028a47d89d0d36a0f20aed510c366ab338f074a941b404fb349caaec821e0850a627777cc8f5abce6b509290027a2a28ff1db62a5ed2f95fc6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c6ae8b6a060917cd498aa7874ad44baff73efc89a023d9f3e9d12c03d0b7f5bcb5e24e1bc2ab2f2c67b9a9d36ff8beb51b5affd4a3510361001c80642955b22ea4bf28b81a5affe5ecdbabd8d17960a6af3825a4522fe76b3d720b5d06e66bff5379d7a8de1f5cc3e7bb75163a854d77d9b3949bf904b6c4e568682f0dab7f217f80da7303cfdc9a53c17b6b51d8ddff0ce49541e0c7d7b2eed82a9d6be4aec73274c30895f5f0f5fa"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "606c9a15a89cd66a00f26122e33ab0a08c4f73f073d843e0f6a4c1618271cfd64e52a055327deaaea8841bdd5b778ebbbd46fbc5f43362326208fdb0d0f93153c57072e2e84cecfe3b45accae7cf9dd1b3eaf9d8250d8174b3dade2256ecc8c3acc77f79d1bf9795a53c46c0f04196d8b492608a9f2a0f0b80294e2abe012dc01e60af94323c467f44c536bf375cddbb068c78432843703dd00544f4fff3eaa1a5a1467afaae7815f80d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "88b383cb266937c4259fc65b9005a8c190ee6cc4b7d3575900e6f3f091d0a2cefa26e601259ffb3fd03083270eb63db1ffb8b4515ec454d12f0944f8f9f6869eedc2c5f1689766a748d74e79ad83ff6a1639aefdec6109342dead31e9cead50bcc00c5b2206e8aaa47fdd01397b141880490174141a1e6e19268378c1b54a84aba60ca711fd72f7df88e120dfea2caa140085a0cf73342f3c588b7edfb5b5e5ccabd68a32364746d92d536"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "dc0b293f1ba02a326743509f41efdfeeac1efc45137ac03e397a3273a1f586a0190cfb4ea96d6c13ca692a4de6de905c8338c3e29a04cbae76272f568b9d795cea5d758106b9d9cff6f80ef650d6b7c428ea3946c3acc594907fe4227ed68faf31f2f6775f1be5139dc0b4d73ed6308fa226b9077561c9e4c7a4df68cc6b819b0f463a11b9a09682ba99752c4db7aea9beac1d9279f2c2675d42b551d27aa2c1c34125e32f2f6f45c35bca45"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5d801a7413311e1d1b19b3c321542b22e2a4ccbe340545d272abede9223741d9835a0fc80cc9da97a13f8bb4110eb4ad71093efba165b1edad0da01da89d86726e0d8e42ae003b4b50297d233c87da08406f0e7fc58ba6da5ee5ba3d2d7142cbe6632734eb2e7b7863c15cc82198ee8f9a0ae0b7f93bdbda1ed269b3824d5d3c8e78513815b17a4c0cc8c9706b9c77423a309ae3fd98e1e05cdbe9e2577834fd71f964301b10b66c316a2d8f2c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2fd32a2bc15a9e96a100624404fd0a4e54ba9f8c0543d8ccf7c5c2e35f5e8c3c11dfd497320aa903900a4ca55a2b323b3ac4a7cfcd01bf0b448db8829072bee6b77c3d7bec2e1d8b414d907288d4a804d2379546ef2e2dc628269589164b13fceb32dba6fd5d48a956ce0b5c3eb28d894a95af58bf52f0d6d6cbe51317152744b4ccfc918ed17fa6856478d580b389016b772e1d02e57d2217a204e25361d91d4845a3fa20fefe2c5004f1f89ff7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f537b437662759bef8bd64368536b9c64fffbddc5e2cbdad465c3966b7f2c4bc5b96767ef40a1c144a4f1cd49edc4cc5b57e7eb30d9b90108f6fd3c0dc8a8808b9e0bd13aa3d661c4863637c5e4ba286553694a60bef18801299ae349df53a355051dcc46a7d003c4aa613808f430e9db8ca7dfe0b3f0a4c5ab6eb306aeb53e11a01f910064fbe6ca78b2a94fac34a2602f73de3f275953e13ff5c6bb5c39b82321ead17ec0f8ecc479e6afbc926e1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1dd9fb7d5b5d5074971e69300720014deba6fbdb942bd29704cdfcd40fa5281d2a1b9f5b776183e03ff99c29587f10e8d325cb49c5c93e94f5132741b92c4086eec1374dea5c1e772cbb230c7b31f3e962eb572be810076bdb926b63732522cdf815c3ab99bbc164a1036aab103cac7b823dd21a911aec9bc794028f07b7f839bae0e68211286441f1c8d3a35b281fd321312577bbda04f643ecb2a74ec4527bb5148dbccbeba749f5ea19b6072366ba"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5bd63737449de2d20ca63943953338ecf4cdd6cd0a726241adb04376385a809cc6ba0f3482a310746fbc2cd5eb214f03a14cdc548777fb0d048d659cd75a962e490c4fe47affc2430a34b10275e4c76752a115aae3a24d4fb4fad89ce4d79d65de10292f3490bfdaeabfae08ed51bda6ec8230e66cb07ddbeec26e3ef68dd71c852900659fcf0c963f4574ffe4626a33db9abf0873dde68b21138498b81e8cc44d354be4073615889a7ddff633b5447d38"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a683ec8250506571f9c640fb1837e1ebb06f123e745f95e521e4ea7a0b2b08a514bbe5bdfd316903d1d6a05f5a143d94dab61d8a3a146ab40b2d6b72df2f0e945875a8aa7051ed115975f6f1567cfcbf04c5e11e3a7027b8e179ba00739181ba10b028e3df7259d0712f4a6cef96469ff737865b85fee2c2db02a6423e32505381e18a1e0b4ce3c7998b8d6b1b5e09c3a280b85486d0984c9e193b0ad2043c2bc4ad04f5b00a73956715937eebf6b3e27afc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4df9d160b8e81c42930c48956fcb46b20b6656ee30e5a51dd6317876dc33e0160d31280fc185e58479f994991d575a917073b4439919c9ac49b6a7c3f985211d084c82c9d5c5b9a2d29c5699a22e79de3958d7b0e856b9aa97493cd4563aaa04fa3977a9bb89e0bc06a82296bdc76d20c8d393770176d648712454305fdfcf4e117d05acb5a5b006a9f8d0dc66dca708c4e4103ca825d2331750685c44ce3d9b3e753455580f4d6ac4533edeeb02cebec7cc84"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "67bb59c3ef5ee8bc79b89a673e331e581215076cc36b68f517ca0a74f74efafe9dcc240e6d8ca4b21019c27d6c9289f4419b4f218eeb39eb741c5ebebfe0ed2f6faeec5e8c477acf71907990e8e288f4d4049111779b0635c7bbec16b76493f1c22f645745fdac2b383679fee573e4f47af45ee08d84f63a5ace4ee1c06fa41e2e6e14b7bc392e38426813087a3a461efc62ed1941dc8f1728a2bdc04fde72a0b786558783c84abd4bd100e4926979a0a5e707b1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "d341147169d2937ff2373bd0a9aefa77968ec8f0d993c6f9881eb174a1911e05cdc45993cb86d149a754bbe321ae38363f9518c50dd3faf087ffeeeb6a058b226ccab7858c00ba6de0e8f4d034b1d27508da5cc473f3a413189ee6fd912d7750486912944d4dc34405ce5ccc3885fb0aabcb922bcfa9081d0ab84c288022bd501235a835eb2e1124ed1d48fd4f8682da8e7919321031326502273375625c4e3a7282b9f53452195e53c6b4b57cd5c66f621bed1814"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "27e7872a54dfff359ea7f0fca256983f7600236e716e111be15a1fe72eb66923ea60038ca2953b0286447dfe4fe853ca13c4d1ddc7a578f1fc5fc8598b05809ad0c64a4363c0228f8d15e28280837a16a5c4dadab681e28968ae17934639fbc124bc59212138e494eecad48f6546c38366f1b7b2a0f56f579f41fb3aef75dc5a0958b25deaa50cb7fd1c69816aa9a51874a98e57911a33daf773c6e6166cecfeec7a0cf54df01ab4b931984f54424e92e08cd92d5e43"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "13dcc9c2783b3fbf6711d02505b924e72ec6736131159017b966dda90986b97522bf52fd15fc0560ecb91e2175322334aaaa0097e1f3777c0be6d5d3de18ed6fa3444133486068a777443a8d0fa212ca46994944555c87ad1fb3a367db711c7ebd8f7a7a6dbb3a0207de85851d1b0ad2f4149bdd5a5ba0e1a81ff742df95edee850c0de20e90dd01753137cb8f2c64e5e4638ceb893a3879ae2c049aa5bce44d56bf3f325b6c5029b2b8e1b2da8de7d4e48ca7d8f6fbdc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9ca875115b109eab538d4ec7023600ad953cacdb49b5abe263e68b48eafac89a15e803e838d048d9625972f271cc8f36344bed7bab69abf0bf05979a4cfff273b82f9961626509765fcb4b4e7fa48212bcb3ab2b1f2dd5e2af768cba6300a813514dd13e4d269e3d36548af0cacdb18bb2439ec9459f6d847d39f5598304ec46a26d75de1f9f0c2a88db915bd26e45e1f1e68c5b5b50d1890e97a3803c36755f026863d14176b8b57f42e91d3ff37787f9b38e333e9f0433"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ec006ac11e6d62b6d9b32ebe2e18c002353a9ffd5dfbc5161ab887770ddd9b8c0e19e5321e5bc105add22e473050b71f0399327c7eba1ef809f8667c1f4e2c7172e10e753705e9a083f5bce88d77521225ecd9e89f1e1caed367fb0275dc28f620fbd67e6b176c9ae5d2659e6ec662116c9f2bbca3a93043233a4861e0688db6dc1800f752c5d58aa5033c250c891d9126e534ed921a9026eb333333fa8292059b8b446f336ca6a0cb4c7946b6aea3831653122f154a4ea1d7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "23deadc94481ce28188f3a0ca3e85431964cb31b60fabf381e6bd45ef0332bd4dde774b0281d317dc2e7d0c298fcf8625fa734126968df8b68ef8a35c325d84ba4fc53936ff3ffdd8838d2a8cabf8a9cac54aa444ed9875944e55994a22f7fa8538b1e983b57d9215fac5c0052029644044e790ce2f5044655608c1d7ad3bb862203ba3aba3b526606f273d342ed5721648e3f600942d3f7546f679161436389d879dd8094e1bd1b1e12cde15cd3cda4c30a40835665e4e5cf94"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "94701e06340114f9cf715a1fb659988d33db59e87bc4844b1500448960af757b5282f6d52967a6ae11aa4ecfc6818c962b084c811a57724f5d401191567f24ce917e4f8c3963474fdc9d2c8613c16f62446448b6da6eeae54d672825ed7606a90e4611d0e318ff00566862c955b636b5e81fec3362e8672ad2a6d222a515cf410482836deba092a51a4d464dfbbab35c50a33437ac16a88256e9e23ddd3c827cc58d3e5000ee90b12e4c5175c5733662d4848ae0d406c2f0a4f498"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "735b0758d5a331b2304f01081172eb95ae4115de651b1a6693c5b9543de33df25d9f421dbaeca033fc8bff57313b482778005aa9fdcbca65c643da2f3320e34197868eec3848ff3c70d7ac7d910fc332e9a359f892ae01641be253013b554a0d3f249b3586b1857e5a0f9482ebd91432a852b221f4287a6e81ed24e8064645d5b28ab9a13b26cc1420ce73dbc47b31acf8a871601022ce23bc443b1222ce9a037a2fe5226295feb4efd4fd671338f459ae146032697cf82fc55c8fbf"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c48d94f14549352790079fee69e3e72ebaa380510e3581a0824066413e7044a36ad08affbf9b52b21963d2f8e092ff0ac1c973c423ade3ece5d3bca852b894675e8173290529226939c24109f50b8b0d5c9f762ff10388833d99bea99c5ef3ebb2a9d19d2231e67ca6c9056d8834730605897426cd069cbeb6a46b9f5332be73ab45c03fcc35c2d91f22bf3861b2b2549f9ec8798aeff83ceaf707325c77e7389b388de8dab7c7c63a4110ec156c5145e42203c4a8e3d071a7cb83b4cd"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "553e9e0de274167ecdd7b5fc85f9c0e665be7c22c93ddc6ec840ce171cf5d1d1a476743eb7ea0c9492eac5a4c9837c62a91dd1a6ea9e6fff1f1470b22cc62359474a6ba0b0334b2739528454470f4e14b9c4eeb6fd2cdd7e7c6f97668eebd1000bef4388015630a8332de7b17c2004060ecb11e58029b3f9575040a5dd4e294e7c78e4fc99e4390c56534a4e933d9a45460f62ffaaba25da293f7765cd7a4ce78c28a85013b893a0099c1c128b01ee66a76f051dc1409bf4176e5afec90e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "dea8f97c66a3e375d0a3412105ed4f0784f3973ec8c57b4f553d3da40fd4cfd39761de563ec96a9178804641f7ebbee48caf9dec17a14bc8246618b22e683c0090259e3db19dc5b6175710df80cdc735a92a990a3cfb166461ae713adda7d9fa3c4cf9f409b1467f3cf85d2141ef3f119d1c53f23c0380b1ebd728d7e932c535965bca41a414b6ea5bf0f9a381e098d282a554a25ce41980d7c7be75ff5ce4b1e54cc61e683f1dd817b8e2c1a430d7f895e5e7af13912cc110f0bbb95372fb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9dfda2e2f732867e60ed2b5fa99ab88eb82dc7a54334d02031258beef75fa4bd6962a1083b9c29e4eeb3e5ab8065f3e2fc732675b8d7705c16cfb4ef7305eb58120f1af5ddc55872a2cbde3a48661a0598f48f63e2e9aadc603545e2b6001748e3af9e86e1830af7b84ffd3e8f16679213d37cac91f07af0af02b37f5ed946ef5c955b60d488acc6ae736b10459ca7dabeacd7dabcfd656511ac913174f6d99327be59befe3e463a49afbb5235f0ce2840588c6edfbaaba00a4211c0764dd638"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ddcd23e8b9dc8889b8599c721e7f8ecc2cbdca03e5a8fd5105f7f2941daec4e2906c654210bdd478374ddee43ee749a920ee91872e057a1157d384dcd111266221b3c79774476b4862fe450704ff2c5353e9a936cac87c96515c28ed4c830335a55d084cb5873c5fd2dd907f3266d8eb7bf13b6dd7cd4966982a0949efd8e428dae13daee549e01cc3c226211d6307823f742c5ef2155601a4644c46eddd603d4abd959c6d242e427768df3b1e22d87971df58a1564b38311a897c85b497a72556"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "39016647acfbc63fe55a74598bc1956eaf4e0cb49d532c5d8323fc6a3f15a0231597f06eafd74ad245e672bf6b21e4da503cb5bf9d15e9038ef354b38807564d91f38b4258378ccd9b9420a1562d7136196822a1291c913d83c4cd99fd8d420990c72cdc47607124de21da8d9c7f472fdcc780379f186a04da93cd87628abf323c8dadcd7fb8fbade37d7d2b5c9f9fc524ff77494c98f42f2158a6f68c906105ca9e8bb2df463863cfc1e9008d8344f55c4e3203dde6699b59812d49ce1279fa1c86"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "02cff7567067cbca5911664c6bd7daaf484181edd2a771d0b64566c3ab08d382e83932cdd7b4dbf86c9cdd1a4c353a511e68afb6746a507a9cd385c198246f4543d606c6149a5384e4ff54c1b90d663dc7a4b91aeac3cf716db7ca6f9a1914e3a33efe82e7ccc4215999c0b012782402db4726db1d7d1c73571d45739aa6fcb5a20eeb54a84d5f99902a8d356cbf95f34c9c28c8f2badfbc08c69233514493c0c04963268c88bc54039ab2999c7b06cba405936dfc43b48cb53f62e18e7ff8ff3f6eb9"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5764812ae6ab9491d8d295a0299228ec7146148ff373241a510faee7db7080706a8dada87938bf726c754e416c8c63c0ac617266a0a4863c2582412bf0f53b827e9a3465949a03dc2db3cb10b8c75e45cb9bf65410a0f6e6410b7f71f3a7e229e647cbbd5a54904bb96f8358adea1aaa0e845ac2838f6dd16936baa15a7c755af8029ef50aed3066d375d3265eaaa38822d11b173f4a1de39461d17d1629c8df7334d8da1b6401daaf7f34b2b48d6556ae99cd29ed1073926bcda867421832a4c36c7095"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4df3043cf0f90462b37d9106e67366d112e4938c4f06abae97869531af89e9feebce0812dffe71a226de5dc36be652e26ef6a4be47d9b2db5cdd43809a565e4fc0988bfe82037c505dd276b757b785203249fd083fb474a25acccc9f38dc5164ff9097e05989aa6e280739a755231f93670e7226e22046914c155bf33d135b3f736ccca84cc47ae643215a054b54b7e13ffcd7ad73cced9279dc3210b80700fcc757acfb64c68e0bc4da05aac2b6a99d5582e79b303c88a7ac4dd8ed4289516bba0e243527"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "bf041a11622715426c3a755c637d5f478dd7da949e50f05377bf333f1c62c671ebdbf9467d37b780c25f7af9d453fc67fafb2f065a3f9f15d4c3561eeaa73fa6c813bf96dcf02430a2e6b65da8d174d2558110dc1208bdcb7898e2670894c0b9e2c894da3b130f57a90ec8ea1bffd27a37b4da4645c546b2b141db4e2c919154dac00e78dd3eb6e4445974e3bb07905982da35e4069ee8f8c5acd0efcfa5c981b4fd5d42da83c633e3e35ebdc959bd14c8bacb52212b4334f94aa64d2ee183861db35d2d8a94"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a170ceda0613adc9c3a1e427f07beacf3b16ed69fb42b6bc09a38d803f632ad2929dba215b85683b74e2feb1d18fe17d0ea0db84d1be4e2e73476917a2a4cff51d6eca7c5e82232afde00dd2286a4c20eb09800b4d5d80e7ea35b6965b9792d99e399abda8cf32174ae2b7414b9bdb9d63e148f7357635a7310b130c939593cd3479164724011966c4232142df9966f09422f34f20b30af4b640a2c6d3dd985fe0ba3dfa9083cbb9b8dfe540ff9f6c608d18481213040768ef33300d773f9890c724ead320a1e7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "929477e9c2d0bbad3429a0e0de776695255013108261dc6404cb09828770e274d8bb650a50e490dfe917fc2047b0f8ee72e105927d9fa70523c727778cbf6ae876d641ad562938c870d12f2e047bb78920739dba0c3f8ce1fb77589623a5f1625f5d6ab81940c7dfc3dc3a641d82b2813629bab8282999317d6b93842334f123fb4693a9c2c9d8ba9bfc746642dfbd045cd2021b272eab7358aa954d453da53fc5392dfa7eb881f6f53809b692d27f3366595ff403289efcc691e118b4744a1147071d8909bef1e8"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3e98bb14fff5bdf7db38a3960dc55ca7d02333daed8712cca13dd5bffd114636559279db72554cc0a0ee1f7e15557d77cab0f2f1131f94fe698db81be38300a856a5eca85e5cf915fb7b6f38ccd2f27350e62cc30ce10ffe835118be3d435d2342ed3d06199b7e20c8e34d68902f0ab8745bd8b7d5b863d525c1f5906d2dca598db8a0f1e67736182cac15677579c58b8c670cae1be3e3c882153b2aa2988933e579ec2d6dbb00c671da64443dfc027dee6dfc3233c99758304570a982bf9b2eb59ccd70d0b54c4b54"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "aa12c7fa50ffdc2811c1872e4bee15f43e6909212385c872eb489f7e06dc1787043f56126f8373bdfa4b3f61405c73dd4dfd3f40aa5cd207e8520849c26f67716a46c0989a99efff42f24e0736e327af8e607c401a1bac77341e9a78c91e35d55b2457bdd5317a405a1fcf7a2a23de68ef92b65819e8aa3807c545361dfc9fe89125123492da958dc313cb5d03cb4b192c54ac6b27fcbc498652f5ed36b587bb74942b3ad453a8d79e5ddc06ebf806dad5046b73251064582ef5777dc530f8701701761884783fdf197f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "83e615cf6e17a29e63945710b548a6d9935850eec69830841e26cb6071e908bf72c87cf079ffb34c5eb1a390def72d004a9488224a18e189aa1092a0f1135712834d257a53dc1d0e2c6417d8f472ff13b181910f4c93a307420d44beec8875d5219a3160b8e921434ddf3f71d68db1c1d5c39d68edb7a604792f8b4e31ecda7895c99fc7031a5b98a22009c1da005ac8fd2da0b5d742743f5712d12fd76d11a18e487776ce21ca0d6e5ab9ca6d8c394c321b91c14e291399a642721361811a73b7392e8603a3004e7060bf"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ae1a8f7bfe4b1a0fa94708921dadb2c20b938239d7b9a2c7c598528f20f49764d322ebe85a5b2ea15563cf2f2304baf55d6607c52e2e1160859dcb7af6d7856899eada0e9128a180d3de6fed9334ba52b80c5c362d5591a0ec30f86d37a399927eb1c53076a12d26775522c511c83eb5b7abc2a00bd2dfd5627a8febba53d85f9b74c4b7f0c862ddb0d9298899b646b774d6cc23e4e23ab47174fccd34499253996d5e0917210e2f6daa1685f89f2f1fdfd5509ebc38191d539ecfb54ff0f5bbe6ef36ea35d425af6462f518"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1d033e06be253ab800c8176d3a9650ab2a5bcaa03e11ea95fb9ab3834b41eb0d1b2bcecfe219364c3104ef65a8d692bd77c798548b7d9a8faf7f5172db24ec7c93006d6e9839368291b8277a82c034a3731f1b2e298d6e0282ec8a7902e4f844d132f1d261d171375c646065e201849f2df73e3748d853a3122c2206aac92fea448500c5418ecfb3d80e0e6c0d51f85831ce74f6c659cc291f5348a1ef8b949f1b2a753633e382f40c1bd1b2f44748ea61127b6f568255ae25e1da9f52c8c53cd62cd482788ae430388a92694c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "104bc838b16a641749dcf73c57b207ea3bcc84381170e4ca362065a3d492e892b426a1f4fd82f69461d1ce1f3aaf8fc291ea30d6667e7e1aea4c44f7d52a5fa6d34709e6658483260ff5da76bfb74e7d194ad40dcac00daf0e45e74db4bc2248100a8b256b257278c3c98f1f2e3a80cdb812352aaf4155b3a4033999fb9fe7f506994fcf3a8db31e9e5ca8ef8c2e9c6326ca5b0803724ba641950eca877fe6ed6afc2e014651c56d0e6a61eaff7c5ed0b861d4bebe42904c0a568c26aa8abb2e97da2bfb40f14eafb6bf16cd208f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5b92e4a175437d0a53eb10de2c56401720b11715a034459ebf506c3fd6534b5e817a0f09deac4bcfd353301d8d031b1331582ac09189b48e6ccea444655866c4bbd123d45ebabb774f877cf12d33b84cfca4a6a94f3f98869fcf2bbb6cc1b964c2438c2f348bcdf9001dce60a4706d20c169a040baa61cbeb0b8e58d505e6e3739ab03e110ae7efdf91347474033defbd1e86af322ec6456d3394699ca7ca6a29a70d9b10a38fe666eab2858bfe12dacb31568549c826c15af5b6fddf779954351be1872f04e53db7b3b5fbf61fd18"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "401cc7bd9f8227efaed70dad83fc8db3bd38efc166f0f11ab142c565c68ba9db680423a3d698b6f3476ef440051fd20b93f6a2ed045825567df5a65e3f62e4442ec396ad260a16a13a1dee46c7e8d88bdd7edf223ab76a9a787c1f4fe9925c051a4ca0e77a0e78baa29f36d193c862fd3a60653f544ea9e3f75f2f553891be8c1fb882f6a6aad118f576f3c2793efc67221b37a45ab6137434f6228cb002fc137b91fb8572c757f00736879453d64a8a868c131810ffdad9e9d028d132157ecb1da675d54047d19b27d3258c9b1bca0a"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c20cf0354982ca6a19d9a4dbf78f810934db2373941a12c263adefa61a5f385c859bc47028829c531dc25ccc0004c7510e707175a102ec3c4b4c933e3f52033e67476ff5f864c446c042a21e6037f7798363d20267891b965879fde80af6b59d77862e3a229af01b7ac78b578e94bd9f9b073c38a627c1864df0083aabb17024bdab6c3c0f0f73d31d59480523a2f23b78baa0385c15f290114305d7f98786b7dbc17a8c2aad97448e8ea389e68ef71091a6a9735ac12ca5497b9171da11a93c28d3273f58b74e2e46279d3ce9d0b20d19"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "e2365c2754073b511f16a1881ff8a537541ca7362ae7b84223d3c7d1d49d03a37d6d05dd2b819af9705c015dacc9dda83474eb14b7d5fce6e8a8f8c58e870149338d320e5ae476da6749af45e65ffed550d225a39dc74ffd93ba7da476985d6f44e90fc8e82454496260458431804d802fe804d825f611772f9710667377adfb1a11e4275bcecb42175c515f6a9439a359824f82cc9d480954364e6693099a821ace362e6c7ecbe68be8823bb5b49b4f23ad81b64139e3b63d9d4d298a842f013ef0d91ce7915ee8f816c70ba2aa3994216f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9c43944676fe859327096f82049cf69e48b98715878400fdf2805e0d5ee642e6cc9c43739f418b701348a033c5cb96bf8702fcd2fac9be58262a843c1e4155ed8a1724b6ebf7cce659d88a95a0c54deb2d7d9574a45219b6419ee173d1d8fad3ace47c962b349abe1048565df85bbd0eb9b11698258c23598023a00fdd26573e41951452027125c6e894a97736ecd63fd15b29a55d8dd9dab7e2e18f541a2e341890a61b7c896e7dc67aa82f3479dacd4a8ec7558d40c34d9ae4060e13718d676c2450258d83de8a86e012813693098c165b4e"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1c707c29582d98a0e99639211102f3f041660ca03ad0939fe3855b8c1b22d6a9b8673c93e3eabc0ab231509b2b0d73c76a290a363943d12d2ff0ea30c6dd54eda753767effe04cabb4c3966388fa4c83a1906a0f48519a5fba9aeb585e0f8c45d6123a75ebe98fd1d0272f733a3925119481a321fe7509346c05128302851ba17a137f956f184e057a305e79a148727a5926de6854eb0314d5492fd735fa773d99ea34c95ca7546bd3a3aa8e66bcc6d860cec3d35d0e2165d5fbe8be99b6e7967df6693e5a6243e94c9c4a2528ae6305cbeca209"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8f1e88103ffa378f062cade0ec509bec99a5c73fb273e79dbef24abf718ac26ac23dfd2b8932038ed3cb9637b71643c161142019f45b25b4fa4c52356737a27027e805ec635154327a66bfe64efc6285cca98c34edc7fb6c0766970a545342cf840aec0a5ba1dd3c6949be4fe97b0f8c8186de07536fd9074db34d09b2f08af9dcf9424d6edbf9cd044102c0e5dc35aff78c36d079dbd2c500e19c8c985ae2abaf6b2a20716bb719754a8840ce97632116c4d0b0e3c83ccca27f11c4204b76b5d6cfe6348a9615d8e4af53500dc4c2cabf12ec8c76"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "b9a0c28f1a6156992c103a84655fc6e654fa6e45e45819513afa797024717c00cc195994512fd53ecd1e12dac4d2448e0c40308382312084d2111f7db147b2e6589ce6d977f6115f629508167df8f45bac98abd49f6b272bcc4fd874dd5e29fb6daceb2d727a2a892194cfb9269eda00626ac89b4e74bd29b21e9f6ef18cb69889a02d4f0a06a2e5718899c1dc3b051c2cfa29653e782f87fefa478e6465bf5ff27f8b6abdb500077aac97100bd955ec535a587d66f23354be51cd8170289344bac9451f74e8aee3639f7c09981f4885e018912324d7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "456844a34ae1074246f8f71eeef2010ec8733265bed7c1cc60043d770edfa320cbd4284a94be2574337e16d27f125074ebd7e99031f7abb4547b9540a7b0b5148ef501b550dd929f3dfe39ac65519f563e9254424aaafa05b1d37c16c771882e9e25d4906ac58603da749adf686932cd73d81e2658134fe69294c7a521d257eaf2110c667fc9d6f09b52d24b93910e532184eeb96eae9d9c9750ac3c39e79367431ac1af7011172d0a8be46a31010219a0310a733068c589bfc4748f3626aa4ff8d355cc893d05111c287c9992e95ad47481a6c42d6eca"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c5c4b9900b9727bdc24baa544cad5faf8340be6b3759361f53889f71f5f4b224aa0090d875a00ea7116772117dbefc3a81c6950ca7ceeae71e4ba975c50d61fec82e6d9448d3a0dfd10bb087bdf0673e3e19fa2aaa7e97eebf71f11b86034fcf5a61240c71444ac3da15ef09b27b3523d37d309e8722380f835c1aee4a767bb027ec0674040853e5b53d6a31657f51acff6d2487860becd5ce695696cfe5937f4a0217b69e01cc6facc24dfe5f5230b8692a0b718e3b3c789d682db36101795a9a5f8bbb838c3679be72f7941a1db180135347d0a884ab7c"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1781df2fedd2c39137854737d054cd3ed16b0ade411e41d97888ac900fdb46d9ae26b3d2dd07e118fd57eabd0dfd03a55793c76420666444865371adffc9b2f35068a0d70f9cfda1ac27ccb4beff4ffa5b8bb8bddac843386675c38a181fd0d935d6d51b25d78e7ff4ecef27a9853c0f0d2879c395ed1c4883987d123890d04f851c3e042e1164c68c0d503de16816f4b0e554236e5f4c339ea11d01ce652f6208f78f457a2417a97c0a6a240f443262def4b6763abf53e597bf1a28f907dc7cbdc751a234ea7d75710ad5ab0c37e8e9805102a375abd44011"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8963552ad1e729ead07750df599d734157aaa4bcdcac17e8eb19b4f99cdb162686ff433137aa4e8a0cc8df0053999196262115aec326cf37567d9ba4760e0ad21d5763977f1ab9b35c0fc667890fa87fc946ceb776a811b5adc69446bfb8f5d9908029dc5aa38db816e4a4e8f98e5a48cf0a01627031c5bd1ced8bc1940dcafe4ae2f1199b186468eafc07e96a89d95dc18ef0fed3eda5b58ce58f221a47ba5311313cc680367eeb058fafc7bcadce5f520b6371489d9e529278ae6ee2650a85aed82896879038bbd9aa8d685fc9528943ccf2235cdf69a86464"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "23ceae3008085134433f5de4b47bafe0f443d443491e6cd47b216dd2dcc3da65239515a6e6b9beb9a939ae9f1f1f5e11f88326475e0962f319d9bf75ddfb4a46e7cc3f799d7547f3c0b2e089018b75787b82ea1a7295e7411f4852f94c94170e98bb0647923b8eb7d184038e56560da46085540cbfef82b6b577c445d038f6c93fbfdfc96ab3a0191d20a57b8610efb4cc45cd95198198e6f80ac46b0601511885f650eb00992605be903bcb46cd53c360c6f86e476c4c9ca4ad052eb572bbf26eb81dd9c73bcbec137aea6ee27aa97dadf7bef733fa1555019dab"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c0fd31e82c996d7edef095cccfcf669accb85a483ea9c59f368cc980f73da7202a95c5156c34192ae4ebf773c1a683c079b17ac9d08b4265b4054fcddaf6666ca50f38f1a2ef2497459a68c06837363a526e850ecfbd223f55dba67db017eadb7a9139abb5bf3854834478b838aafa16c5ee90ea52fb2f7b8db2bcefb85b06fc455c2b6c27d0af9a49dbf2f313bf2599370637393e7972b31d8bf6759f3e6115c618e672831f84d76ba1879c754144e1df4d56b1e264b1797dcb8ab165040c8d20b931071081d7f74fbff590bdc8e888e71acc6a720270da8db7c821"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "936fdab91fba396e4a8754a97a04ba333daadc29885c9d0c8fea3387165278f4974e468fea57f2bfd8428c4d0f010833283db73735d39de0c0cb5898d0c06c0ecd05f61098935cb6130a8da60d1a6c2ecfe420f972263fff5a631b09e81c837183c5528bb1c740b36fc39cb082f3383c2b4afb25d04ad1d1f4af63dcf26a0bf5a647cd2e35a51cc119c4dc5031f5715b3bfa1f2b92de06bdac0d670fdd30980f32c51f3936b51e5db6b95a8d36279da5faa4c4e454f2b7e54e9f488071011c7f6f9b63da260a2e46d796d36c9a9dcae88085806a10a77bbb670d475778"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a55fe162b287bd6eebd6cf7e7aeea8672322d924ae42c7404ff89aedb98943f3755d2889bca488cc7000e6e9b8e7a0ef289273cd29c44cc600e330d1775e3cb767f12150e1615dca8c3f67466463a3ca993a1b788cf67a7a35b95dfff954206eb5ea1e1bf7fb06482a551625b5c9fd9a86e8414c8cf79d3a14104a153cbe04aac5172aa4c4a89349f5856c4262dd1d7317a7544c9afbbed449e7dcc2b58d9df6c9c9ed3883e42e80f5c2433550f30e73c7bce0fccdd880adc19282a392dae26a0108e7faf168cfc15937aeb046d60712603286b8ddfb27916b79242d56f1"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2bd6976592408cdbc4e41dcd3ecfbb786775ddedef914d9058e6753f839fdfe15b17d549dbc084aa6cdf3befa0158aa84c5d58c5876144fd7e6c41ab7d42419d0dd353732e0e6d3fafc4f5626c07433390a4fd467197e85b5de7e2cf1c26cc575356adedcc0740008523b503df12ff571387726c5ccb280376d19cbacb1d7ce7aab8b13292c6a8b8881e949cbf6d4610d16ebba1d46cdb8d0459596e0aa683d0307bd926e14de19b9bfeaefa29d91b82248604673a455520cbb64eef3f38cfad8e126a3b1cfa1aaba53a784c8ae0c50279c0ecdab54095d36f67ace9b8ebbb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "71913ae2b1c8729ed6da003c24a1d4f96e28d7faf55ca14ee0b2865282b9b61103ce6ee0b00b00aacf2081adedea5616f9dfd22c6d6d4f5907bcc02eb33edf92de0bd479794f51246d9b612b4543f6ff633c4fc83bfa6144c9d26721cdc690a3d5a8db54d8bc7873bfd32924eeb502810732b5ac2f1852bb021c401d26c39aa3b7eb09083093a9e89bf889b53383b5af61110aca1b9fdf38908c7d5a184fc5f46b3423a66a2749feb8de2c541c563987278dbd0513d99b732411012b5b75e385510de5f6839c3797dc094c9501d5f0504b06b43efb6e746f2129ca189c1da424"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "9d048a83294de08d3063d2ee4b4f3106641d9b340a3785c076233686dd3382d9064a349c9eaa78028d35652078b583e3f708e036eb2ced3f7f0e936c0fd98f5d0f8aa91b8d9badef298bd0c06843831279e7c0c67ca7e572f552cfdd984c12e924c08c13aeec6f7e13d161785546ebfd794b5d6a92a4744e52c4cab1d0df93b9468be6e264e8cfcc488f9c3c1817cbe501f4b9cc5999483b7433aea777226b25273a6ef2331b5f3b6db8091591e8e276015da3ef78bb2ee0526ffe23def2d8d193cbe594e8ced1f3d216fcedae2a1eb288da82e34cf98aebc28def658ee0849ae7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3251c96cbf82ee2e5264528c0b6cdfc23d20e1eb2d6441b5d62f0fd24c692a0d45a8bc8aac32884b7141ac0f4f113ec9fc7f6b4db3d696374177f9a42d602ca471275b928f639105a55b846da9ac7274cc37de8c38541f6895f94d72a81e117844b46601c201f7189b935a96e42505f2098ac985d92dfe86349a706ef6325b3c2e4060ced3c453e68ed09e043bcc75846b80118dc53530248da250fb57922d0afa53a7b2c89161aa4fa372a46b2a8e1307741cecedf585d2f998a9d496763800b6965c38a5d8aa566c709f13699c8185ab4fd8fdc8b824f4dd6d1c255b4788f50574"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2de31dbc8a012254586f3229d3524fc529554e98850d30acdfc11406bba6a142029126ac165ee90b2de7509fc3571a8ee12e16b05054eb8baea879d135b39627f0d8331be3e66bc720c2096ce74e437daebf3bc53d8f2ccc228c3256d3edb6e9ae7c354a0c9350e6d663a9a30630bf9da3d96b96608a2a171ae28105714058b6c4b38a36c56561c4612c32aad25c65b7fb6faa4e4ecd44ebf9b2fad42ff9a807cda2581614fd30d41a7436069399b8d4f062a37a5bd4066a93d541fa5797a7d3e7dc9c4c40f0bbf5256f71613240f9ef128b3423eacaf428ada06b6a531f835281e4f3"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "07dadee629a08223dcd7ec441287b4c5e26347451d9c003e3a8496b4ea313b51126283a6720d7851e24423d9c9c818b4601247178f38a61f45fd4c8596d79529d416834226666a2c8552bbc901cc5cc3406a18fc88077fea52e1b620748553052ab7788c0d025b095b736fbe714cb3a968ec16b5917652eba2d7cf32ef3140d6c27b25d053e9786d24cd09a5306a0ef55e46201faa6196a91084267d7a7b5ca57c2efdeb2cb97d682d2a191b915553c8933f1d1b7faf0b4a1d83ef611f1e44438bc1c3d860fbfd12b5f26e5a6889a31ce26ae6a55c7a563b5816d113423ef3f25fa9befc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1d94166bb387526d519c4ce150221954da8930f66765fe6a5504e30a69962d595cfdd07a82c003843598864261f053bdb6f5086d516c261e089caa89990f0967605768ae9200bdfe4dcd7b77a93265cb33d9851a2a1036113c732bf3f37534530641300f0620de5c16101e16f4baf39d9fcbfcb01c52afce0992c329d8dbb438c314eee995c5020611d6f889e06b8a032785cba9a415580dbf752b5e510523c89f478cc6f047bd926f51e4a965c9749d1e76379c0e7e5b56803893bafaa4d2892b4c52f143b2fa777cd1035ea418684b8019df084f9a3f1f768753096621f342895c510d01"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "fc0073f199ed8a1d6edc8e7bdf182670003108d82b283aba82326e856f8de378987a03d0fe8d2041440fd29d51c63796aab44090d2b14ee00859b3a08cbe88f724badcd3c401226c5db8b307b8deea5be305412b080e9f99cf79d6d08d3646f347a7afebb62912e3e246e2e726f9aec5c101d916e47f984507b1d65d313697256c77da7eca3bc5811c87bee02a2826cefff0d92bae989609aaf95d70561b40d98474c37277c884aed887a1606d206b11e8a8a71d1f1d19319557b57351228ff0404be700a6cc56c0a30f3d4b7a0a046463fdaf19e7d5f59e155f378e35baa33db1e881f2207f"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f42a6a91278d6a076feba985b1cf4ce0af1fa9d6d039c136e8971e665ff088a10b6b9a379a6f5526fc5957773a0ccb8972a4a19be0745ac13937030a54b18dee4f4c5df47a58a33a7516b90e646e5da999166ab0e52f457f7c9b7e391836a687eaae37b377e59a4c995ab0c57162c307ab951a9ba6590f429cd27250e7010eb794ec1b1ec35f8aad189b2fd3e8aff24d93601d91a4884e6f84b02757ce7620a02901519fccfda52f68ad6df709d112a9c25d66bcbb9622806427ca8b8d346b6db05874bde800cde9cf17df4b05baab0f133febd1ebbb053b49c109a7f5b1f864a304d10288e2f0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "bbcefaf4a0739509f8a2f831c954071aac52e60cfa882a867b8b910dcf7edf92e1c0692bb027bc378c460a01cb6ecc8f2a012dd84ee5a678cd497b1457b6d393421fbee98ff544fc7eba24cbc3aae506254d9a2d74dde74437ce4c8a69010718506bf4c5943342a942e5e2d3406a3016280b6e37954c5d5e763346251afb0b746cad68cac757f9df765e092518729cfb9a5e76300c124e708ca33591a369767ffb63933cb72fba67beb2223d98984d0b75eb5d1a38615913747b520b3d613c715c0c77d2987bb88f3c419bcc5d38573cf4a8a4f550b2d876f05ca252d88c70a561d869a5018b32f7"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "dc2437010cb05d9cab2af5c275e1d2acd627ce19fb86355df91fb8d059e60d591663c8eb077d48388c9a321057a98136f49f0098348d9f29d808936f98bb1787c7ac75fb14f6076dfd2de5b59b1fa4848cabaa9a99a091dc24b561911c392ecdbe53f4adae82b852d830adea3a10490c908e337ce0a6d12354ce05a37ad3a06696b66820af8a1f67e6287533fd6f38a5f6ad1c6b078c08baf2c37d2683af01e6a5b33796c8ae48935a888f9bd265f4f11a4e27c433b8b1c9afd140bcd21a07e24378ad6badde8e47c57e3340f49e2406e8d49afadd65eaaa4c3d078c27d7e42118cb86cd248100a356"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "6c290db326dd3152e6fa9b9c0cd7d49e50a0221b96e32f5f34a8cb7d0c2edd3e937a7d025d6999b7b468add4d6894d8f7aceaabc18f4d9c171f1fe95ea1ae8570382a8450fbc595d95b1f51d24e1abc2970b0e1d20ca40aa21bdfb3656adf2f19882eda606f5ef1c03174e1d94c8d12f0fee8dce6852f42a364eeafa27a7971d4379405db8e46baac4d685b969238e5df06292a6c790bf1994a051b038e1d8db91e1bc4804f32443781c34a552ed2e8100cea374e77af56ba0e11c45990d3ba68df9087b1f4968cbcbb1c42f99b7267c76af926ff3134e093df28fab039cad420c6b70f2d9b5e678c155"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ac724a22ebabaedbbb052953e3c264a4b6440f313bad501cdc1484b64f33402a2230898776db5c818c28035ffae6ea24abd04b7159e42159833903a0c23a7c564f7645e49ddedb748fd9e51bd6cbf2eced98caaa35226970f003ce1fd260ac5795e096f1c04aebf8fd36e5e2adeea929b5e963a3cb71d6b55c85bb7d3a2b03a7e74b4416de8fa68950168d7c3ae8ed2e29bad1e8a182a7c5418e5d564373163778cd3c34e9d320eb1a60480a8f98b12e0026cbd7752e6079812e3767d9f55f3f10b8c214a6eceb2a58954091a06b33862af171a9b60bf2c6a44e8766e6c56e98092c56f2a8510f6d05c103"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "8c70114f7cffb375c2b9a06e27297a5c32418b2daf68af5bbedcc7106edbc070e764bf40c1f8eb15079e2ab77f898afff3490108ed9afb7ea9cb05df41d263be0e42d2321d3d2656622d7bd232bf68d37375fe7314b09cba66f19c8b59424198ee69e7a9f3de0ecce0685127807ce336fa479ccaf7aa1ebc4e406271ce6c4923ec36093516498cc227f9218869346c80ba5ae83e023aca0ae2bc86b5bf5d115a4616b6587cb869d92f8c780ab70d5766de07a204af5e1c8dbba622516d2e911b36c82e4687e4d258ea616c07f76ff0baa376c8d5975cffac0b25817f779ae3ce88b72eb47e378484ce999bf0"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0733d59f041036398233fd47a84b93f6778ae5259ef5d62aa3b9faedec34c7edb570c18b2a5d2c4c55cf656d98a1ae396d45a3b746b7ad6f07312c3d05d1a50ffa90bcdcdba105e25b7b0c52664223f8c2476925d46dc6ea2406ded7d0b0b292f6656cebcc7616cfa4b82aec68b35d1da67f6ed2bf0171849d6bb65128d8a140ea5cf97f1003f8d7093bee077be78def4f7bd2caccbf0644f26b26285225142c40038484c3bb9ba9597744f4389e76dca3eb695c33ccc621cab1fb603cb3535a0ad318d220385d5e94f8674f3d55e97e097f8d5c049e911946afbfce783819951d65d6bff4567dc951390d1aaa"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "398ddbba3dcb5642c102efa841c1fcdaf067062e7eef8e2ee0cd73d7f77e57372d6ee1a9b7b6f86ad12d575001ae71f593449cb5a476c6bfeddaa2af0f9239c1d7effdedf66ceaf413707b5ab9661a7cc0ef8cfe4d1651579c4f0f64e2d12a52653c54f2dd60864e769eab8a627c89c56ee93365d031f0d2523cb95664b1575d51b122f33c9e94de75432a690658c977b68aa5b721a393f9b9b3b612c10e920a7d510c6d8460b35f8614c42f5d2c241a01b28105aa7c1b521ac63ebbedafac6d5a38c898e8590f918a1927bc53aecc2b1c8b18d7df9107c6997d9b3fa4b0bdb1c603da619d9e75670b97a5b40f06"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "ef07bbc7c4150dd47f8c69a7989948fe831dc798b0424dcd6551bfa8e88216095a7e5d720909bf3d23526b9ba464b66ff6b63a7337c31451ab9a15f04ead809a62bb52206237de77597a730106d02d227dd6099ea9ee2a92cdc446ac3b9d024e32255adb3e9b56b561c431e0b5a721f0336f19568a5335d0ebc6c73ed8ff2c15e219477d9e4b67f2928e251f8a61a2848857e037d010806c718ab062967fd8e85f3722252957923f5f9005aae47b4b1b3fa464e3ba9df573a56055f17e903126fbbcb6cb96de92fe617c97f84ef3ba0d8f2651dc4aa80c157f372ae1bc02e5067ad076f3fe48bb72c0f3c99273f82b"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "c7076986d2333f3a6752adf11f1a9e5c6bc4755f341073cc86a9c7519c8db029d5ae833fdf3fee826ff4692c57880c5074620ea97c00f1dde1e8a0f18501627984ded4d1b5c4af35be5cc1bcc868060a49a968dc0547acde490b4c68d79924a93a986aa0ad060c7de706e8a99ce8f84a4f8707b52a8ee122b763ba580d6b1f35f6af25094c69f49247da96c836991851ad36f60bf577863d7471608a012afa7a56656abeee7cd9b4f1f4d9d13a8526c0f33cd251caf7486639e787250390e7e488e9ec311fc3d847a7266cc59bcc2bc34192554aa57cf25db10ce04bdabef3fde6db85f55195ecc2ff892b2e268ebea6"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "01789f40d42d8d3e4a416fd9ae7de78c3a30507809eda200e1afaaf8d7020cd1fad18eba62d821946f220506cf105ff0e2069a771a2c233714afa6b2f695497e4b95c9693dbb93ec4c9a14720676aa87ee31dd34e4e081756477032b4a57b328285f2cdec1b269754c474936927e93acc26012aff1bb36f30c2402aca0a9b9ce9568f5000e2c934263933b436c94f8d6589c89db7edabc5d03a8fe795fe50c5166beab64ed7c22662b984ae2c66dbe4c090b0df603b27c759278f8d66859afea3f6a8f02c2c2a2202b9fc29132256f164b5050a803b43688dc4c9ba86374a3522afba5d1a19bb3820b883aebc267627095"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "2c61944bd6a50da00ebb951d2b67d79fc6b6fb5aca83b1de3dbd7690ab756bb1e1a21051ccf1e24136ac8ccb42a2ee10be94d2cb9289d5f52b6f90e9d07a3478f36a1eb7d08c3dec52ca154fd1427ba92a4ecbe73a71bceafbd26e9a39d50821e2876d3a0c0e6e373b9795dbf72ea29cc439ff42706be798c90d4617b39c90ec84bf9fb699dc8a9a34e25d81759d6c57df45efb1d0d68aa51278564b99633ed5dc464bb7d53c5c21f798f33bcd868657ecfe75a1ed8149d394b398969ef624831b30f1458465bfd2fdf3f284f2ffc54bf2817b5fab2e02056e864f78bb6fd870c64f3609dab218f25da8060f756e45121e79"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "942fa0c68cc72f69518a3a7aac0cde45bab0e928b5cb2bd24d049fc313f74b6afa87c4e34150484f3b5200163f8a6472d04777928ecc49319539fc17d71a38090f55a74f757fe45781a3c09f08dcd3dd4c73c8533a5e00cf8a86ebe77fe45be2848574f7c5d25e9a0632a60d2dd41febdbf987d2a0487e4a4ce6ed5f49f2d741a88ecac232b1498253fa4ee8147bbd0f600abdf295e81f7570015aac5fe6ca7bb4a99bb3fc54287106d7fc1132a574af49db82a7b9a5f33e193cde527ca2176c52cdab672165e0fe5720f71ada57ee90060aa069ae2a0bfe67c1b71b17c601c3c2224bf9891bc11ba216e3ebcb51fd95b8d7cb"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "0d68cfe9c087ec116fe7572042385159cc705960f842aabad1ed1387ec1697f4413a23c6090041328fedd4b626c6eeaac5b5a71acc1fd1bb8fbd228857ac5bd045c364be7a5a26338ff04c99c4c473cf445a891db6422d1bdef4533442df171643fc36a092fabb464298e4194c9e2950884de13d113ee24160a416404c16ddc5d2476cb3fb80da543e6ed9105f6003977acb34e1fdd2cbdf7a00d5ff84350b74ac231418c0d88269d02d824802791ff42a51cc835deb9869a6023f867f82ef6dc0bfb03e6dfa835646bb18a4074773486e308aa39e532aaea4e6fb35dcada7e060f8282c371ed26d22302323d4fd142a85534671"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "45e24b167a0bbef1bd8f79dd047763d0754f36a7b623f298059d177e8ac994945c37d2c4af06f01318960301595941124592f2995af1459d854339998d3ae17534df2d9793d6e203857d02c98a0cd88991e641b3e640090ba303f87b907dca8ca462fac19ad079b2c82ea5b521ab891b10138b083b3d9fa214a8fe60d1cb3599c5d199c61a2cfb7ee2f39e5a5abad5ac4998b707545f73e92128d21803420526d2598a53bb314adf29a0ef56b94bd2221601eb53ecb8540e8fffd38fba7bd827ef255e4ef55491475c0f383a241f81c72af4e1dbf2a65cd4d18a497615aa0de2791a3511a7977a8d4d41492bfa4085f2fd4e8f751d"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "1c1bb695ae90e6e33fc1e8b2a62ab98bf835ac7193440f2351c8cdd830472b637d2fd9c9013cb83caef506abc1c4f7567706db6046b1d184579c7a9223ab1b35e32898c70a3c27628123ffcfa518612f080a2c4a9f8e0a927a47dc98307d2b48de9d5dddcb5c82f0b0e4e610d44f1baa9bbbf7f5a727134680bb7d1327b73b52d8e5e36dbb53971e99e699d79f75a3fc01316bd7012947d119d6aeb7f75b8fbf0479c03002148553fa0da450fd59d4f1bebc252caa11ed9bec5b6ef54279b5f8382b61cffc67ec03f4baa7ea476c31364b86aa8ccad9fd0818717f0ced2dd49477874b4341c602d7a1beab860eb476c7e3ce597e6926"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "7a3cd9bb2277e2c7f1134fe7233f0f7883c2db9fba80aa5742b03041de0fe589d9e5ea84470dabf41bb66816f3e33ebf19a0ca5aba1004cf971249b258ff26a98dbd0c37ec6cd574854109433357720040bafed4531e0079186b1e853e0ced35d08d27f6d732ed6e2c6651b51cc15c420a24f2dc36c16ef4b3896df1bb03b3963f9aaeb02a48eac5772abd5948c2fd0db2bb74e3351e5eabd681c4f413655bd94dec96b1544c1d5d2d1df4bdc26020d25fe81d5238de824687a5505e1fbe08d11b3924b3ccc070fd225bf01eb79e3d21f7b62a836cd3bcc11c931669c37613470e356143df87c48848a829f5e018973a5db88eb6c60203"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "3f158afd0733fcc5dfe1efc2dd4eada732f942af734ee664955bb1ba613eafd0f349e7554a14d68200c62d8f2dca2ec8b81c8350735eaf437041f78b452598825b6899560963ade66a0fc74ad01f8343d1d19c7bb327a8dc14ffdb1c42fa72b2970d9155e2da6a2e6419d4117842d826ff38ffab9617307a0283d3ea28c8104ad9a6e087bb750ed1d10fd8f7100b1663682e979d80e43968c33d9eff66f4d1344e583ee521e78d0a2193c0577516b978339c143bfc689bc744bbc4a9163063de82c9706384b6b385e54666c86b34f23c1e25be293af06092ca31d857e11e5b2caf0d19dd3afbe85380878eda76d718b4bb869c67e044e242"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "a177af4387b9bfa3d59e97ee7b0ff5f4ae4a326fd9204c8d28831a67fcc385ee6c4828247b16d11aea9bb8cd9e6c4d2876c6b2fa6d5041ad39e1b04039071e29c4d86417e7eac4fc7d3823958a021823e2c880a757dfbcd0c8196371db5bbfac15e4d1a0596508b6d26f8c4a664924c95082d173f817995b44c4285d625d9b2f56c86632fe1295c5a8a7a3760028072bcb07bc245a705e7174d06b9d5c0c8ca495b9ac218f1921fa63f2db3fd148f07545366d008fb5aead7497d902b91fbaa39669929d4ae9d07df8557f1f0aed7b51252f10c6606e5ff3ede1327530ca356b4896ecf14bf7322d77fddfbe28d52f6de7f66eeb81704c87e2"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "01a15b9018e35cc342c926b01d03ad9db4993a6bf92e0555969fee90033f28f3ec234c1268b11b040dfa0770d4ceb39edfeb8ee6a589f4eebcc08d2d1b0a1a52953aa26eb44fdf4a2743c3dacb212a0c0f325572f645f53027b6f3c0c55abaeb1b0918c89bedcb5028f094d743ea354f8ff553c45f111a8fd5a14a4e5c835164747d302472e19a67da04b4c8e39756a9d248ce14d1ed43de75aca86850f2455eccd4639b2af035bb3f504cc9065d091c1c47e036083cb3fc50bf39292b11737c7ce0b49673ba93981de304dc65a671775b6ff927e3ff93850b214fffb5792105a4bdc81354d5b09e84afbdd1792b8fb4e9d0ae3dad2492b03282"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "24f07ae31279ceed18ec6d35990f21200934ad6b132c6c62e82fe92a40a0e60a5bed10720eff5a1f728971888682772b2d9060d4fee88f37d0824e7384dddcc549475f0e1a44eda4804778b62febe46e04657a20577ee70acb3425e334881eebd8ddf714ae8c527ea747e3367de384e595a43b299b6bb3f6b0a4716cf90038e0f75a47d5057d7fcc3c8a8f9224992c67f8ae0d3251ea09a24aed9ce57ab637f6b3cbb7083df62b6287f64d0877984c4249d113bdb2b07865082aa24cd7ec07061b17de320f51f29f25b82d7073d369cf2dbf96310c0c311997911b2cc02f606f9cd99663c57e78499192a2a78f9c9fa67013e0f9817287faa69b22"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "4aeb32bf9d050f10bea18d9f71b4afea7bd08550e574e7d50df234c7413668b297b6721d7a0f0bdcdcceb2f55adddea28cd59bd44be0c5ec067039e428706caae11f565d961ad6e7f4c51b0aed6d05cc5b8d826c4b9c39daefb6c7da46dce619a359dc9ce215a215218fa8d54ee0b4f301b6c201c7c2c5f7cb1c6e0cb76ba6c6e8f63ef7a5213d550b0d0857fa0ff9e3e38e497161617413ac066e2fa539520233193a5cb7baa0c2cb20b45e56bfed2c40a9544d1f230dd0cd6d4976e7cf51da8a13200c3957c0154c8237b2931ce19b824963ac576ea49b548cc6aa85c47796b470fb2c6308d88f390bb13607e294c84a838b2713b14ca6a5e8bcee"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "77e607478be5502432230c913d9ec82f967d87c0ee169a74076f989648853eca693277287f8a5b306bc94dfdbf64ca5cb5dfc0bc498589d51a691b8d57d4b0a9ee247d038fe1b5571183be3e75c37045bf1235863ff1b84b208c10e7f1a5ba54ff36af5b2870129867164d013e0a6d2cc067a3509bba2f46390302c80b651cf590ef69aad8effd94cab28a9b44be6a38b58cfc47c9c725d6fa467894163383b6873d10d263b1cbbad932ded59ab503920267ac026726f794a335a88f6ef564f8968c6fa6f5d3ea161eb6062ca349b9a0e4038273399cfa297a6b07ceda1ebaa99c9de2d935ee230a08c5a488ad46f3393243371d40916b8063cac9da63"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "50957c407519951bd32e45d21129d6b83436e520b0801ec8292d79a828106a41583a0d607f853dc4410e0a1427f7e873455a75df065cfc6eef970f7e49d123b346976460aadd91cf513c140c356442a84656904a8b1d708dc6089db371c36f4fe059c62302eaab3c06c0cb3b429961f899dcf99798464b8571a440cac7a52b495f32417af6bc8f58adc63647531f804b4e96273b29b42434c1236bde80ba3744fef7b1d11c2f9db332b35bc25123338ac9a0796aac213c9709b3c514ea7ecd80e22d3d8a74f28c8194418a6e1ff30714d0f5a61c068b73b2ba6cad14e05569b4a5a100da3f91429d6e3ffee10ceea057845ec6fc47a6c5125b22e598b2dc"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "f2273ec31e03cf42d9ca953f8b87e78c291cb538098e0f2436194b308ce30583f553fccb21ae6c2d58f3a5a2ca6037c1b8b7afb291009e4310a0c518e75314c5bb1e813bf521f56d0a4891d0772ad84f09a00634815029a3f9ad4e41eafb4a745e409ef3d4f0b1cf6232b70a5ce262b9432f096e834201a0992db5d09ffa5cbc5471460519a4bc7cdc33ae6dfe6ffc1e80ea5d29813136406499c3514186ced71854a340701519ef33b6c82ca67049ab58578ff49c4c4fbf7d97bfec2ecd8fbefec1b6d6467503fea9d26e134e8c35739a422647aaf4db29c9a32e3df36e5845791fdd75a70903e0ce808313a3327431b7772567f779bbaee2e134c109a387"
+        },
+        {
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+            "5784e614d538f7f26c803191deb464a884817002988c36448dcbecfad1997fe51ab0b3853c51ed49ce9f4e477522fb3f32cc50515b753c18fb89a8d965afcf1ed5e099b22c4225732baeb986f5c5bc88e4582d27915e2a19126d3d4555fab4f6516a6a156dbfeed9e982fc589e33ce2b9e1ba2b416e11852ddeab93025974267ac82c84f071c3d07f215f47e3565fd1d962c76e0d635892ea71488273765887d31f250a26c4ddc377ed89b17326e259f6cc1de0e63158e83aebb7f5a7c08c63c767876c8203639958a407acca096d1f606c04b4f4b3fd771781a5901b1c3cee7c04c3b6870226eee309b74f51edbf70a3817cc8da87875301e04d0416a65dc5d"
+        }
+    };
+
+    public String getName()
+    {
+        return "BLAKE2xs";
+    }
+
+    private void testBlake2xsTestVectors()
+    {
+        for (int i = 0; i != Blake2xsDigestTest.xofTestVectors.length; i++)
+        {
+            String[] vector = Blake2xsDigestTest.xofTestVectors[i];
+            byte[] input = Hex.decode(vector[0]);
+            byte[] key = Hex.decode(vector[1]);
+
+            Blake2xsDigest h = new Blake2xsDigest(vector[2].length() / 2, key);
+            h.update(input, 0, input.length);
+
+            byte[] out = new byte[vector[2].length() / 2];
+            h.doFinal(out, 0);
+            if (!areEqual(out, Hex.decode(vector[2])))
+            {
+                fail("BLAKE2xs mismatch on test vector ", vector[2], Hex.toHexString(out));
+            }
+
+            out = new byte[vector[2].length() / 2];
+            h.update(input, 0, input.length);
+            Blake2xsDigest clone = new Blake2xsDigest(h);
+
+            h.doOutput(out, 0, out.length);
+            if (!areEqual(out, Hex.decode(vector[2])))
+            {
+                fail("BLAKE2xs mismatch on test vector after a reset", vector[2], Hex.toHexString(out));
+            }
+
+            byte[] outClone = new byte[out.length];
+            clone.doFinal(outClone, 0, outClone.length);
+            if (!areEqual(out, outClone))
+            {
+                fail("BLAKE2xs mismatch on test vector against a clone",
+                    vector[2], Hex.toHexString(outClone));
+            }
+        }
+    }
+
+    private void testLengthConstruction()
+    {
+        try
+        {
+            new Blake2xsDigest(-1);
+            fail("no exception");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isEquals("BLAKE2xs digest length must be between 1 and 2^16-1", e.getMessage());
+        }
+    }
+
+    private void testOutputOverflow()
+    {
+        Blake2xsDigest h = new Blake2xsDigest(1);
+        byte[] out = new byte[2];
+
+        try
+        {
+            h.doFinal(out, 0, out.length);
+            fail("no exception");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isEquals("Output length is above the digest length", e.getMessage());
+        }
+
+        h.doFinal(out, 0, 1);
+    }
+
+    private void testBlake2xsUnknownLength()
+    {
+        final long maxNumberOfBlocks = 1024 * 1024;
+        Blake2xsDigest h = new Blake2xsDigest()
+        {
+            public long getUnknownMaxLength()
+            {
+                return maxNumberOfBlocks * 32;
+            }
+        };
+
+        byte[] buf = new byte[32];
+        for (int i = 0; i < maxNumberOfBlocks; i++)
+        {
+            h.doOutput(buf, 0, 32);
+        }
+
+        try
+        {
+            h.doOutput(buf, 0, 32);
+            fail("no exception");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isEquals("Maximum length is 2^32 blocks of 32 bytes", e.getMessage());
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testBlake2xsTestVectors();
+        testBlake2xsUnknownLength();
+        testLengthConstruction();
+        testOutputOverflow();
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new Blake2xsDigestTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/BlowfishTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/BlowfishTest.java
index 757788e..a310883 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/BlowfishTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/BlowfishTest.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * blowfish tester - vectors from http://www.counterpane.com/vectors.txt
+ * blowfish tester - vectors from https://www.counterpane.com/vectors.txt
  */
 public class BlowfishTest
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST5Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST5Test.java
index c651d87..2ec1fb2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST5Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST5Test.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * cast tester - vectors from http://www.ietf.org/rfc/rfc2144.txt
+ * cast tester - vectors from https://www.ietf.org/rfc/rfc2144.txt
  */
 public class CAST5Test
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST6Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST6Test.java
index ef035a5..4e29b19 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST6Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/CAST6Test.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * cast6 tester - vectors from http://www.ietf.org/rfc/rfc2612.txt
+ * cast6 tester - vectors from https://www.ietf.org/rfc/rfc2612.txt
  */
 public class CAST6Test
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/CMacTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/CMacTest.java
index 47160e7..bf81f69 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/CMacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/CMacTest.java
@@ -18,7 +18,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * CMAC tester - <a href="http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/tv/omac1-tv.txt">Official Test Vectors</a>.
+ * CMAC tester - <a href="https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/tv/omac1-tv.txt">Official Test Vectors</a>.
  */
 public class CMacTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/CSHAKETest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/CSHAKETest.java
index 72ba40d..282eda6 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/CSHAKETest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/CSHAKETest.java
@@ -99,6 +99,7 @@
 
         doFinalTest();
         longBlockTest();
+        checkZeroPadZ();
         
         checkSHAKE(128, new CSHAKEDigest(128, new byte[0], new byte[0]), Hex.decode("eeaabeef"));
         checkSHAKE(256, new CSHAKEDigest(256, new byte[0], null), Hex.decode("eeaabeef"));
@@ -107,6 +108,23 @@
         checkSHAKE(256, new CSHAKEDigest(256, null, null), Hex.decode("eeaabeef"));
     }
 
+    private void checkZeroPadZ()
+    {
+        byte[] buf = new byte[20];
+
+        CSHAKEDigest cshake1 = new CSHAKEDigest(256, new byte[0], new byte[265]);
+        cshake1.doOutput(buf, 0, buf.length);
+        isTrue(areEqual(Hex.decode("6e393540387004f087c4180db008acf6825190cf"), buf));
+
+        CSHAKEDigest cshake2 = new CSHAKEDigest(128, new byte[0], new byte[329]);
+        cshake2.doOutput(buf, 0, buf.length);
+        isTrue(areEqual(Hex.decode("309bd7c285fcf8b839c9686b2cc00bd578947bee"), buf));
+
+        cshake2 = new CSHAKEDigest(128, new byte[29], new byte[300]);
+        cshake2.doOutput(buf, 0, buf.length);
+        isTrue(areEqual(Hex.decode("ff6aafd83b8d22fc3e2e9b9948b581967ed9c5e7"), buf));
+    }
+    
     private void doFinalTest()
     {
         CSHAKEDigest cshake = new CSHAKEDigest(128, new byte[0], Strings.toByteArray("Email Signature"));
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ChaCha20Poly1305Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ChaCha20Poly1305Test.java
new file mode 100644
index 0000000..042049f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ChaCha20Poly1305Test.java
@@ -0,0 +1,446 @@
+package org.bouncycastle.crypto.test;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.macs.SipHash;
+import org.bouncycastle.crypto.modes.ChaCha20Poly1305;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.Times;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ChaCha20Poly1305Test
+    extends SimpleTest
+{
+    private static final String[][] TEST_VECTORS = new String[][] {
+    {
+        "Test Case 1",
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "4c616469657320616e642047656e746c"
+        + "656d656e206f662074686520636c6173"
+        + "73206f66202739393a20496620492063"
+        + "6f756c64206f6666657220796f75206f"
+        + "6e6c79206f6e652074697020666f7220"
+        + "746865206675747572652c2073756e73"
+        + "637265656e20776f756c642062652069"
+        + "742e",
+        "50515253c0c1c2c3c4c5c6c7",
+        "070000004041424344454647",
+        "d31a8d34648e60db7b86afbc53ef7ec2"
+        + "a4aded51296e08fea9e2b5a736ee62d6"
+        + "3dbea45e8ca9671282fafb69da92728b"
+        + "1a71de0a9e060b2905d6a5b67ecd3b36"
+        + "92ddbd7f2d778b8c9803aee328091b58"
+        + "fab324e4fad675945585808b4831d7bc"
+        + "3ff4def08e4b7a9de576d26586cec64b"
+        + "6116",
+        "1ae10b594f09e26a7e902ecbd0600691",
+    },
+    };
+
+    public String getName()
+    {
+        return "ChaCha20Poly1305";
+    }
+
+    public void performTest() throws Exception
+    {
+        for (int i = 0; i < TEST_VECTORS.length; ++i)
+        {
+            runTestCase(TEST_VECTORS[i]);
+        }
+
+        outputSizeTests();
+        randomTests();
+        testExceptions();
+    }
+
+    private void checkTestCase(
+        ChaCha20Poly1305    encCipher,
+        ChaCha20Poly1305    decCipher,
+        String              testName,
+        byte[]              SA,
+        byte[]              P,
+        byte[]              C,
+        byte[]              T)
+        throws InvalidCipherTextException
+    {
+        byte[] enc = new byte[encCipher.getOutputSize(P.length)];
+        if (SA != null)
+        {
+            encCipher.processAADBytes(SA, 0, SA.length);
+        }
+        int len = encCipher.processBytes(P, 0, P.length, enc, 0);
+        len += encCipher.doFinal(enc, len);
+
+        if (enc.length != len)
+        {
+            fail("encryption reported incorrect length: " + testName);
+        }
+
+        byte[] mac = encCipher.getMac();
+
+        byte[] data = new byte[P.length];
+        System.arraycopy(enc, 0, data, 0, data.length);
+        byte[] tail = new byte[enc.length - P.length];
+        System.arraycopy(enc, P.length, tail, 0, tail.length);
+
+        if (!areEqual(C, data))
+        {
+            fail("incorrect encrypt in: " + testName);
+        }
+
+        if (!areEqual(T, mac))
+        {
+            fail("getMac() returned wrong mac in: " + testName);
+        }
+
+        if (!areEqual(T, tail))
+        {
+            fail("stream contained wrong mac in: " + testName);
+        }
+
+        byte[] dec = new byte[decCipher.getOutputSize(enc.length)];
+        if (SA != null)
+        {
+            decCipher.processAADBytes(SA, 0, SA.length);
+        }
+        len = decCipher.processBytes(enc, 0, enc.length, dec, 0);
+        len += decCipher.doFinal(dec, len);
+        mac = decCipher.getMac();
+
+        data = new byte[C.length];
+        System.arraycopy(dec, 0, data, 0, data.length);
+
+        if (!areEqual(P, data))
+        {
+            fail("incorrect decrypt in: " + testName);
+        }
+    }
+
+    private ChaCha20Poly1305 initCipher(boolean forEncryption, AEADParameters parameters)
+    {
+        ChaCha20Poly1305 c = new ChaCha20Poly1305();
+        c.init(forEncryption, parameters);
+        return c;
+    }
+
+    private static int nextInt(SecureRandom rand, int n)
+    {
+        if ((n & -n) == n)  // i.e., n is a power of 2
+        {
+            return (int)((n * (long)(rand.nextInt() >>> 1)) >> 31);
+        }
+
+        int bits, value;
+        do
+        {
+            bits = rand.nextInt() >>> 1;
+            value = bits % n;
+        }
+        while (bits - value + (n - 1) < 0);
+
+        return value;
+    }
+
+    private void outputSizeTests()
+    {
+        byte[] K = new byte[32];
+        byte[] A = null;
+        byte[] N = new byte[12];
+
+        AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, N, A);
+        ChaCha20Poly1305 cipher = initCipher(true, parameters);
+
+        if (cipher.getUpdateOutputSize(0) != 0)
+        {
+            fail("incorrect getUpdateOutputSize for initial 0 bytes encryption");
+        }
+
+        if (cipher.getOutputSize(0) != 16)
+        {
+            fail("incorrect getOutputSize for initial 0 bytes encryption");
+        }
+
+        cipher.init(false, parameters);
+
+        if (cipher.getUpdateOutputSize(0) != 0)
+        {
+            fail("incorrect getUpdateOutputSize for initial 0 bytes decryption");
+        }
+
+        // NOTE: 0 bytes would be truncated data, but we want it to fail in the doFinal, not here
+        if (cipher.getOutputSize(0) != 0)
+        {
+            fail("fragile getOutputSize for initial 0 bytes decryption");
+        }
+
+        if (cipher.getOutputSize(16) != 0)
+        {
+            fail("incorrect getOutputSize for initial MAC-size bytes decryption");
+        }
+    }
+
+    private void randomTests() throws InvalidCipherTextException
+    {
+        SecureRandom random = new SecureRandom();
+        random.setSeed(Times.nanoTime());
+
+        for (int i = 0; i < 100; ++i)
+        {
+            randomTest(random);
+        }
+    }
+
+    private void randomTest(SecureRandom random) throws InvalidCipherTextException
+    {
+        int kLength = 32;
+        byte[] K = new byte[kLength];
+        random.nextBytes(K);
+
+        int pHead = random.nextInt() >>> 24;
+        int pLength = random.nextInt() >>> 16;
+        int pTail = random.nextInt() >>> 24;
+        byte[] P = new byte[pHead + pLength + pTail];
+        random.nextBytes(P);
+
+        int aLength = random.nextInt() >>> 24;
+        byte[] A = new byte[aLength];
+        random.nextBytes(A);
+
+        int saLength = random.nextInt() >>> 24;
+        byte[] SA = new byte[saLength];
+        random.nextBytes(SA);
+
+        int nonceLength = 12;
+        byte[] nonce = new byte[nonceLength];
+        random.nextBytes(nonce);
+
+        AEADParameters parameters = new AEADParameters(new KeyParameter(K), 16 * 8, nonce, A);
+        ChaCha20Poly1305 cipher = initCipher(true, parameters);
+
+        int ctLength = cipher.getOutputSize(pLength);
+        byte[] C = new byte[saLength + ctLength];
+        System.arraycopy(SA, 0, C, 0, saLength);
+
+        int split = nextInt(random, saLength + 1);
+        cipher.processAADBytes(C, 0, split);
+        cipher.processAADBytes(C, split, saLength - split);
+
+        int predicted = cipher.getUpdateOutputSize(pLength);
+        int len = cipher.processBytes(P, pHead, pLength, C, saLength);
+        if (predicted != len)
+        {
+            fail("encryption reported incorrect update length in randomised test");
+        }
+
+        len += cipher.doFinal(C, saLength + len);
+        if (ctLength != len)
+        {
+            fail("encryption reported incorrect length in randomised test");
+        }
+
+        byte[] encT = cipher.getMac();
+        byte[] tail = new byte[ctLength - pLength];
+        System.arraycopy(C, saLength + pLength, tail, 0, tail.length);
+
+        if (!areEqual(encT, tail))
+        {
+            fail("stream contained wrong mac in randomised test");
+        }
+
+        cipher.init(false, parameters);
+
+        int decPHead = random.nextInt() >>> 24;
+        int decPLength = cipher.getOutputSize(ctLength);
+        int decPTail = random.nextInt() >>> 24;
+        byte[] decP = new byte[decPHead + decPLength + decPTail];
+
+        split = nextInt(random, saLength + 1);
+        cipher.processAADBytes(C, 0, split);
+        cipher.processAADBytes(C, split, saLength - split);
+
+        predicted = cipher.getUpdateOutputSize(ctLength);
+        len = cipher.processBytes(C, saLength, ctLength, decP, decPHead);
+        if (predicted != len)
+        {
+            fail("decryption reported incorrect update length in randomised test");
+        }
+
+        len += cipher.doFinal(decP, decPHead + len);
+
+        if (!areEqual(P, pHead, pHead + pLength, decP, decPHead, decPHead + decPLength))
+        {
+            fail("incorrect decrypt in randomised test");
+        }
+
+        byte[] decT = cipher.getMac();
+        if (!areEqual(encT, decT))
+        {
+            fail("decryption produced different mac from encryption");
+        }
+
+        //
+        // key reuse test
+        //
+        cipher.init(false, AEADTestUtil.reuseKey(parameters));
+
+        decPHead = random.nextInt() >>> 24;
+        decPLength = cipher.getOutputSize(ctLength);
+        decPTail = random.nextInt() >>> 24;
+        decP = new byte[decPHead + decPLength + decPTail];
+
+        split = nextInt(random, saLength + 1);
+        cipher.processAADBytes(C, 0, split);
+        cipher.processAADBytes(C, split, saLength - split);
+
+        len = cipher.processBytes(C, saLength, ctLength, decP, decPHead);
+        len += cipher.doFinal(decP, decPHead + len);
+
+        if (!areEqual(P, pHead, pHead + pLength, decP, decPHead, decPHead + decPLength))
+        {
+            fail("incorrect decrypt in randomised test");
+        }
+
+        decT = cipher.getMac();
+        if (!areEqual(encT, decT))
+        {
+            fail("decryption produced different mac from encryption");
+        }
+    }
+
+    private void runTestCase(String[] testVector)
+        throws InvalidCipherTextException
+    {
+        int pos = 0;
+        String testName = testVector[pos++];
+        byte[] K = Hex.decode(testVector[pos++]);
+        byte[] P = Hex.decode(testVector[pos++]);
+        byte[] A = Hex.decode(testVector[pos++]);
+        byte[] N = Hex.decode(testVector[pos++]);
+        byte[] C = Hex.decode(testVector[pos++]);
+        byte[] T = Hex.decode(testVector[pos++]);
+
+        runTestCase(testName, K, N, A, P, C, T);
+    }
+
+    private void runTestCase(
+        String  testName,
+        byte[]  K,
+        byte[]  N,
+        byte[]  A,
+        byte[]  P,
+        byte[]  C,
+        byte[]  T)
+        throws InvalidCipherTextException
+    {
+        byte[] fa = new byte[A.length / 2];
+        byte[] la = new byte[A.length - (A.length / 2)];
+        System.arraycopy(A, 0, fa, 0, fa.length);
+        System.arraycopy(A, fa.length, la, 0, la.length);
+
+        runTestCase(testName + " all initial associated data", K, N, A, null, P, C, T);
+        runTestCase(testName + " all subsequent associated data", K, N, null, A, P, C, T);
+        runTestCase(testName + " split associated data", K, N, fa, la, P, C, T);
+    }
+
+    private void runTestCase(
+        String  testName,
+        byte[]  K,
+        byte[]  N,
+        byte[]  A,
+        byte[]  SA,
+        byte[]  P,
+        byte[]  C,
+        byte[]  T)
+        throws InvalidCipherTextException
+    {
+        AEADParameters parameters = new AEADParameters(new KeyParameter(K), T.length * 8, N, A);
+        ChaCha20Poly1305 encCipher = initCipher(true, parameters);
+        ChaCha20Poly1305 decCipher = initCipher(false, parameters);
+        checkTestCase(encCipher, decCipher, testName, SA, P, C, T);
+        encCipher = initCipher(true, parameters);
+        checkTestCase(encCipher, decCipher, testName + " (reused)", SA, P, C, T);
+
+        // Key reuse
+        AEADParameters keyReuseParams = AEADTestUtil.reuseKey(parameters);
+
+        try
+        {
+            encCipher.init(true, keyReuseParams);
+            fail("no exception");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isTrue("wrong message", "cannot reuse nonce for ChaCha20Poly1305 encryption".equals(e.getMessage()));
+        }
+    }
+
+    private void testExceptions() throws InvalidCipherTextException
+    {
+        ChaCha20Poly1305 c = new ChaCha20Poly1305();
+
+        try
+        {
+            c = new ChaCha20Poly1305(new SipHash());
+
+            fail("incorrect mac size not picked up");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+
+        try
+        {
+            c.init(false, new KeyParameter(new byte[32]));
+
+            fail("illegal argument not picked up");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // expected
+        }
+
+        AEADTestUtil.testTampering(this, c, new AEADParameters(new KeyParameter(new byte[32]), 128, new byte[12]));
+
+        byte[] P = Strings.toByteArray("Hello world!");
+        byte[] buf = new byte[100];
+
+        c = new ChaCha20Poly1305();
+        AEADParameters aeadParameters = new AEADParameters(new KeyParameter(new byte[32]), 128, new byte[12]);
+        c.init(true, aeadParameters);
+
+        c.processBytes(P, 0, P.length, buf, 0);
+
+        c.doFinal(buf, 0);
+
+        try
+        {
+            c.doFinal(buf, 0);
+            fail("no exception on reuse");
+        }
+        catch (IllegalStateException e)
+        {
+            isTrue("wrong message", e.getMessage().equals("ChaCha20Poly1305 cannot be reused for encryption"));
+        }
+
+        try
+        {
+            c.init(true, aeadParameters);
+            fail("no exception on reuse");
+        }
+        catch (IllegalArgumentException e)
+        {
+            isTrue("wrong message", e.getMessage().equals("cannot reuse nonce for ChaCha20Poly1305 encryption"));
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new ChaCha20Poly1305Test());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/DESTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/DESTest.java
index 08056fc..58a0615 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/DESTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/DESTest.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.crypto.test;
 
+import java.security.SecureRandom;
+
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.generators.DESKeyGenerator;
@@ -12,8 +14,6 @@
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
-import java.security.SecureRandom;
-
 class DESParityTest
     extends SimpleTest
 {
@@ -151,7 +151,7 @@
 }
 
 /**
- * DES tester - vectors from <a href=http://www.itl.nist.gov/fipspubs/fip81.htm>FIPS 81</a>
+ * DES tester - vectors from <a href=https://www.itl.nist.gov/fipspubs/fip81.htm>FIPS 81</a>
  */
 public class DESTest
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7564Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7564Test.java
index 3589b3b..07ad98a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7564Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7564Test.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.crypto.test;
 
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Mac;
 import org.bouncycastle.crypto.digests.DSTU7564Digest;
 import org.bouncycastle.crypto.macs.DSTU7564Mac;
 import org.bouncycastle.crypto.params.KeyParameter;
@@ -51,6 +52,7 @@
         hash512Tests();
         macTests();
         overflowTest();
+        keySizeTest();
     }
 
     private void overflowTest()
@@ -384,6 +386,34 @@
                 + Hex.toHexString(expectedMac)
                 + " got " + Hex.toHexString(mac));
         }
+
+        // check doFinal() has reset
+        dstu7564mac.update(input, 0, input.length);
+        dstu7564mac.doFinal(mac, 0);
+
+        if (!Arrays.areEqual(expectedMac, mac))
+        {
+            fail("Failed mac test reset - expected "
+                + Hex.toHexString(expectedMac)
+                + " got " + Hex.toHexString(mac));
+        }
+
+        // check that init reset correctly
+        dstu7564mac.init(new KeyParameter(key));
+        dstu7564mac.init(new KeyParameter(key));
+        dstu7564mac.update(input, 0, input.length);
+        dstu7564mac.doFinal(mac, 0);
+
+        if (!Arrays.areEqual(expectedMac, mac))
+        {
+            fail("Failed mac test double init - expected "
+                + Hex.toHexString(expectedMac)
+                + " got " + Hex.toHexString(mac));
+        }
+
+        // check simple reset
+        dstu7564mac = new DSTU7564Mac(macBitSize);
+        dstu7564mac.reset();
     }
 
     private void hash512Tests()
@@ -518,6 +548,31 @@
         }
     }
 
+    private void keySizeTest()
+    {
+        doKeySizeTest(new DSTU7564Mac(512));
+        doKeySizeTest(new DSTU7564Mac(256));
+    }
+
+    private void doKeySizeTest(Mac dstuMac)
+    {
+        /* Define message */
+        final byte[] myMessage = "1234567890123456".getBytes();
+
+        /* Determine underlying engine size */
+        final int myEngineSize = dstuMac.getMacSize() == 32 ? 64 : 128;
+
+        /* Define key (can be any multiple of engineSize) */
+        final byte[] myKey = new byte[myEngineSize];
+
+        /* Initialise Mac (will fail with ArrayIndexOutOfBoundsException) */
+        dstuMac.init(new KeyParameter(myKey));
+        final int myOutSize = dstuMac.getMacSize();
+        final byte[] myOut = new byte[myOutSize];
+        dstuMac.update(myMessage, 0, myMessage.length);
+        dstuMac.doFinal(myOut, 0);
+    }
+
     private void hash256Tests()
     {
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7624Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7624Test.java
index b8b73aa..2af7f79 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7624Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/DSTU7624Test.java
@@ -160,6 +160,34 @@
                 + Hex.toHexString(expectedMac)
                 + " got " + Hex.toHexString(mac));
         }
+
+        // check that reset correctly on doFinal()
+        dstu7624Mac.update(authtext, 0, authtext.length);
+        dstu7624Mac.doFinal(mac, 0);
+
+        if (!Arrays.areEqual(mac, expectedMac))
+        {
+            fail("Failed MAC test reset - expected "
+                + Hex.toHexString(expectedMac)
+                + " got " + Hex.toHexString(mac));
+        }
+
+        // check that init reset correctly
+        dstu7624Mac.init(new KeyParameter(key));
+        dstu7624Mac.init(new KeyParameter(key));
+        dstu7624Mac.update(authtext, 0, authtext.length);
+        dstu7624Mac.doFinal(mac, 0);
+
+        if (!Arrays.areEqual(mac, expectedMac))
+        {
+            fail("Failed MAC test double init - expected "
+                + Hex.toHexString(expectedMac)
+                + " got " + Hex.toHexString(mac));
+        }
+
+        // check simple reset
+        dstu7624Mac = new DSTU7624Mac(512, 128);
+        dstu7624Mac.reset();
     }
 
     private void KeyWrapTests()
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECGOST3410Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECGOST3410Test.java
index 291c305..b9e55e3 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECGOST3410Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECGOST3410Test.java
@@ -100,7 +100,7 @@
 
     /**
      * Test Sign & Verify with test parameters
-     * see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
+     * see: https://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
      * gostR3410-2001-TestParamSet  P.46
      */
     private void ecGOST3410_TestParam()
@@ -157,7 +157,7 @@
 
     /**
      * Test Sign & Verify with A parameters
-     * see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
+     * see: https://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
      * gostR3410-2001-CryptoPro-A-ParamSet  P.47
      */
     public void ecGOST3410_AParam()
@@ -207,7 +207,7 @@
 
     /**
      * Test Sign & Verify with B parameters
-     * see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
+     * see: https://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
      * gostR3410-2001-CryptoPro-B-ParamSet  P.47-48
      */
     private void ecGOST3410_BParam()
@@ -257,7 +257,7 @@
 
     /**
      * Test Sign & Verify with C parameters
-     * see: http://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
+     * see: https://www.ietf.org/internet-drafts/draft-popov-cryptopro-cpalgs-01.txt
      * gostR3410-2001-CryptoPro-C-ParamSet  P.48
      */
     private void ecGOST3410_CParam()
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECNRTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECNRTest.java
index 7c1b238..43a2c3b 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECNRTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECNRTest.java
@@ -3,13 +3,24 @@
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.TigerDigest;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECNamedDomainParameters;
 import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.signers.ECNRSigner;
 import org.bouncycastle.math.ec.ECConstants;
 import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
@@ -29,7 +40,7 @@
 
     byte[] kData = BigIntegers.asUnsignedByteArray(new BigInteger("700000017569056646655505781757157107570501575775705779575555657156756655"));
 
-    SecureRandom    k = new TestRandomBigInteger(kData);
+    SecureRandom k = new TestRandomBigInteger(kData);
 
     private void ecNR239bitPrime()
     {
@@ -80,6 +91,63 @@
         }
     }
 
+    private void rangeTest()
+    {
+        /* Create the generator */
+        ECKeyPairGenerator myGenerator = new ECKeyPairGenerator();
+        SecureRandom myRandom = new SecureRandom();
+        String myCurve = "brainpoolP192t1";
+
+        /* Lookup the parameters */
+        final X9ECParameters x9 = ECNamedCurveTable.getByName(myCurve);
+
+        /* Initialise the generator */
+        final ASN1ObjectIdentifier myOid = ECNamedCurveTable.getOID(myCurve);
+        ECNamedDomainParameters myDomain = new ECNamedDomainParameters(myOid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+        ECKeyGenerationParameters myParams = new ECKeyGenerationParameters(myDomain, myRandom);
+        myGenerator.init(myParams);
+
+        /* Create the key Pair */
+        AsymmetricCipherKeyPair myPair = myGenerator.generateKeyPair();
+
+        /* Create the digest and the output buffer */
+        Digest myDigest = new TigerDigest();
+        byte[] myArtifact = new byte[myDigest.getDigestSize()];
+        final byte[] myMessage = "Hello there. How is life treating you?".getBytes();
+        myDigest.update(myMessage, 0, myMessage.length);
+        myDigest.doFinal(myArtifact, 0);
+
+        /* Create signer */
+        ECNRSigner signer = new ECNRSigner();
+        signer.init(true, myPair.getPrivate());
+
+        try
+        {
+            signer.generateSignature(myArtifact);
+            fail("out of range input not caught");
+        }
+        catch (DataLengthException e)
+        {
+            isTrue(e.getMessage().equals("input too large for ECNR key"));
+        }
+
+        //
+        // check upper bound
+        BigInteger order = ((ECPublicKeyParameters)myPair.getPublic()).getParameters().getN();
+
+        signer.init(true, myPair.getPrivate());
+        byte[] msg = BigIntegers.asUnsignedByteArray(order.subtract(BigIntegers.ONE));
+        BigInteger[] sig = signer.generateSignature(msg);
+
+        signer.init(false, myPair.getPublic());
+        if (!signer.verifySignature(msg, sig[0], sig[1]))
+        {
+            fail("ECNR failed 2");
+        }
+
+        isTrue(Arrays.areEqual(msg, signer.getRecoveredMessage(sig[0], sig[1])));
+    }
+
     public String getName()
     {
         return "ECNR";
@@ -88,10 +156,11 @@
     public void performTest()
     {
         ecNR239bitPrime();
+        rangeTest();
     }
 
     public static void main(
-        String[]    args)
+        String[] args)
     {
         runTest(new ECNRTest());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECTest.java
index 2e733ce..a34ac78 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/ECTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ECTest.java
@@ -11,13 +11,16 @@
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.BasicAgreement;
+import org.bouncycastle.crypto.StagedAgreement;
 import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
 import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
+import org.bouncycastle.crypto.agreement.ECDHCStagedAgreement;
 import org.bouncycastle.crypto.agreement.ECDHCUnifiedAgreement;
 import org.bouncycastle.crypto.agreement.ECMQVBasicAgreement;
 import org.bouncycastle.crypto.digests.SHA3Digest;
 import org.bouncycastle.crypto.ec.CustomNamedCurves;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.ECDHUPrivateParameters;
 import org.bouncycastle.crypto.params.ECDHUPublicParameters;
 import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -825,6 +828,47 @@
         }
     }
 
+    private void testECDHStagedAgreement()
+    {
+        SecureRandom random = new SecureRandom();
+
+        X9ECParameters x9 = CustomNamedCurves.getByName("curve25519");
+        ECDomainParameters ec = new ECDomainParameters(x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+
+        ECKeyPairGenerator kpg = new ECKeyPairGenerator();
+        kpg.init(new ECKeyGenerationParameters(ec, random));
+
+        AsymmetricCipherKeyPair p1 = kpg.generateKeyPair();
+        AsymmetricCipherKeyPair p2 = kpg.generateKeyPair();
+        AsymmetricCipherKeyPair p3 = kpg.generateKeyPair();
+
+        StagedAgreement e1 = new ECDHCStagedAgreement();
+        StagedAgreement e2 = new ECDHCStagedAgreement();
+        StagedAgreement e3 = new ECDHCStagedAgreement();
+
+        e1.init(p1.getPrivate());
+        e2.init(p2.getPrivate());
+        e3.init(p3.getPrivate());
+
+        AsymmetricKeyParameter stage1_12 = e1.calculateStage(p2.getPublic());
+        AsymmetricKeyParameter stage1_23 = e2.calculateStage(p3.getPublic());
+        AsymmetricKeyParameter stage1_31 = e3.calculateStage(p1.getPublic());
+
+        BigInteger k1 = e1.calculateAgreement(stage1_23);
+        BigInteger k2 = e2.calculateAgreement(stage1_31);
+        BigInteger k3 = e3.calculateAgreement(stage1_12);
+
+        if (!k1.equals(k2))
+        {
+            fail("1-2 calculated agreement test failed");
+        }
+
+        if (!k3.equals(k2))
+        {
+            fail("3-2 calculated agreement test failed");
+        }
+    }
+
     private void testECMQVTestVector1()
     {
         // Test Vector from GEC-2
@@ -1125,6 +1169,8 @@
 
         testECUnifiedTestVector1();
         testECUnifiedTestVector2();
+
+        testECDHStagedAgreement();
     }
 
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed25519Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed25519Test.java
index c040d9c..15c4bd0 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed25519Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed25519Test.java
@@ -12,6 +12,7 @@
 import org.bouncycastle.crypto.signers.Ed25519ctxSigner;
 import org.bouncycastle.crypto.signers.Ed25519phSigner;
 import org.bouncycastle.math.ec.rfc8032.Ed25519;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -58,7 +59,7 @@
 
         signer.init(true, privateKey);
 
-        areEqual(sig, signer.generateSignature());
+        isTrue(areEqual(sig, signer.generateSignature()));
 
         signer.init(false, publicKey);
 
@@ -105,24 +106,53 @@
         byte[] signature = signer.generateSignature();
 
         Signer verifier = createSigner(algorithm, context);
-        verifier.init(false, publicKey);
-        verifier.update(msg, 0, msg.length);
-        boolean shouldVerify = verifier.verifySignature(signature);
 
-        if (!shouldVerify)
         {
-            fail("Ed25519(" + algorithm + ") signature failed to verify");
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldVerify = verifier.verifySignature(signature);
+
+            if (!shouldVerify)
+            {
+                fail("Ed25519(" + algorithm + ") signature failed to verify");
+            }
         }
 
-        signature[(RANDOM.nextInt() >>> 1) % signature.length] ^= 1 << (RANDOM.nextInt() & 7);
-
-        verifier.init(false, publicKey);
-        verifier.update(msg, 0, msg.length);
-        boolean shouldNotVerify = verifier.verifySignature(signature);
-
-        if (shouldNotVerify)
         {
-            fail("Ed25519(" + algorithm + ") bad signature incorrectly verified");
+            byte[] wrongLengthSignature = Arrays.append(signature, (byte)0x00);
+
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldNotVerify = verifier.verifySignature(wrongLengthSignature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed25519(" + algorithm + ") wrong length signature incorrectly verified");
+            }
+        }
+
+        if (msg.length > 0)
+        {
+            boolean shouldNotVerify = verifier.verifySignature(signature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed25519(" + algorithm + ") wrong length failure did not reset verifier");
+            }
+        }
+
+        {
+            byte[] badSignature = Arrays.clone(signature);
+            badSignature[(RANDOM.nextInt() >>> 1) % badSignature.length] ^= 1 << (RANDOM.nextInt() & 7);
+
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldNotVerify = verifier.verifySignature(badSignature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed25519(" + algorithm + ") bad signature incorrectly verified");
+            }
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed448Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed448Test.java
index 123b373..1a56c69 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed448Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Ed448Test.java
@@ -11,6 +11,7 @@
 import org.bouncycastle.crypto.signers.Ed448Signer;
 import org.bouncycastle.crypto.signers.Ed448phSigner;
 import org.bouncycastle.math.ec.rfc8032.Ed448;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -69,7 +70,7 @@
 
         signer.init(true, privateKey);
 
-        areEqual(sig, signer.generateSignature());
+        isTrue(areEqual(sig, signer.generateSignature()));
 
         signer.init(false, publicKey);
 
@@ -114,24 +115,53 @@
         byte[] signature = signer.generateSignature();
 
         Signer verifier = createSigner(algorithm, context);
-        verifier.init(false, publicKey);
-        verifier.update(msg, 0, msg.length);
-        boolean shouldVerify = verifier.verifySignature(signature);
 
-        if (!shouldVerify)
         {
-            fail("Ed448(" + algorithm + ") signature failed to verify");
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldVerify = verifier.verifySignature(signature);
+
+            if (!shouldVerify)
+            {
+                fail("Ed448(" + algorithm + ") signature failed to verify");
+            }
         }
 
-        signature[(RANDOM.nextInt() >>> 1) % signature.length] ^= 1 << (RANDOM.nextInt() & 7);
-
-        verifier.init(false, publicKey);
-        verifier.update(msg, 0, msg.length);
-        boolean shouldNotVerify = verifier.verifySignature(signature);
-
-        if (shouldNotVerify)
         {
-            fail("Ed448(" + algorithm + ") bad signature incorrectly verified");
+            byte[] wrongLengthSignature = Arrays.append(signature, (byte)0x00);
+
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldNotVerify = verifier.verifySignature(wrongLengthSignature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed448(" + algorithm + ") wrong length signature incorrectly verified");
+            }
+        }
+
+        if (msg.length > 0)
+        {
+            boolean shouldNotVerify = verifier.verifySignature(signature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed448(" + algorithm + ") wrong length failure did not reset verifier");
+            }
+        }
+
+        {
+            byte[] badSignature = Arrays.clone(signature);
+            badSignature[(RANDOM.nextInt() >>> 1) % badSignature.length] ^= 1 << (RANDOM.nextInt() & 7);
+
+            verifier.init(false, publicKey);
+            verifier.update(msg, 0, msg.length);
+            boolean shouldNotVerify = verifier.verifySignature(badSignature);
+
+            if (shouldNotVerify)
+            {
+                fail("Ed448(" + algorithm + ") bad signature incorrectly verified");
+            }
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/EthereumIESTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/EthereumIESTest.java
new file mode 100644
index 0000000..18e23de
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/EthereumIESTest.java
@@ -0,0 +1,510 @@
+package org.bouncycastle.crypto.test;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.KeyEncoder;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
+import org.bouncycastle.crypto.digests.SHA1Digest;
+import org.bouncycastle.crypto.engines.EthereumIESEngine;
+import org.bouncycastle.crypto.engines.TwofishEngine;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
+import org.bouncycastle.crypto.macs.HMac;
+import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.IESParameters;
+import org.bouncycastle.crypto.params.IESWithCipherParameters;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * test for Ethereum flavor of ECIES - Elliptic Curve Integrated Encryption Scheme
+ * <p>
+ * Note the IV is always required when passing parameters, as the IV is added to the MAC.
+ */
+public class EthereumIESTest
+    extends SimpleTest
+{
+    private static byte[] TWOFISH_IV = Hex.decode("000102030405060708090a0b0c0d0e0f");
+
+    EthereumIESTest()
+    {
+    }
+
+    public String getName()
+    {
+        return "EthereumIES";
+    }
+
+    private void doStaticTest(byte[] iv)
+        throws Exception
+    {
+        BigInteger n = new BigInteger("6277101735386680763835789423176059013767194773182842284081");
+
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16), // b
+            n, ECConstants.ONE);
+
+        ECDomainParameters params = new ECDomainParameters(
+            curve,
+            curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+            n);
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+            curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+            params);
+
+        AsymmetricCipherKeyPair p1 = new AsymmetricCipherKeyPair(pubKey, priKey);
+        AsymmetricCipherKeyPair p2 = new AsymmetricCipherKeyPair(pubKey, priKey);
+
+        byte[] commonMac = Hex.decode("0262b12d60690cdcf330baba03188da80eb03090f67cbf2043a18800f4ff0a0262b12d60690cdcf330bab6e69763b471f994dd2d16a5fd82ff1012b6e69763b4");
+
+        //
+        // stream test
+        //
+        EthereumIESEngine i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        EthereumIESEngine i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        byte[] d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        CipherParameters p = new ParametersWithIV(new IESParameters(d, e, 64), new byte[32]);
+
+        i1.init(true, p1.getPrivate(), p2.getPublic(), p);
+        i2.init(false, p2.getPrivate(), p1.getPublic(), p);
+
+        byte[] message = Hex.decode("1234567890abcdef");
+
+        byte[] out1 = i1.processBlock(message, 0, message.length);
+
+        if (!areEqual(out1, Hex.decode("fb493cdaaa2938daaa2fbbf0886f3b9575c810db240eb9f4adb9089b")))
+        {
+            fail("stream cipher test failed on enc");
+        }
+
+        byte[] out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("stream cipher test failed");
+        }
+
+        //
+        // twofish with CBC
+        //
+        BufferedBlockCipher c1 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        BufferedBlockCipher c2 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c1);
+        i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c2);
+        d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        p = new IESWithCipherParameters(d, e, 64, 128);
+
+        if (iv != null)
+        {
+            p = new ParametersWithIV(p, iv);
+        }
+
+        i1.init(true, p1.getPrivate(), p2.getPublic(), p);
+        i2.init(false, p2.getPrivate(), p1.getPublic(), p);
+
+        message = Hex.decode("1234567890abcdef");
+
+        out1 = i1.processBlock(message, 0, message.length);
+
+        if (!areEqual(out1, (iv == null) ?
+            Hex.decode("b8a06ea5c2b9df28b58a0a90a734cde8c9c02903e5c220021fe4417410d1e53a32a71696")
+            : Hex.decode("34bb9676b087d0b3a016e70a93c4afcb507882a53c5ca7a770913e654ff1422c4b236cbf")))
+        {
+            fail("twofish cipher test failed on enc");
+        }
+
+        out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("twofish cipher test failed");
+        }
+    }
+
+    private void doShortTest(byte[] iv)
+        throws Exception
+    {
+        BigInteger n = new BigInteger("6277101735386680763835789423176059013767194773182842284081");
+
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16), // b
+            n, ECConstants.ONE);
+
+        ECDomainParameters params = new ECDomainParameters(
+            curve,
+            curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+            n);
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+            curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+            params);
+
+        AsymmetricCipherKeyPair p1 = new AsymmetricCipherKeyPair(pubKey, priKey);
+        AsymmetricCipherKeyPair p2 = new AsymmetricCipherKeyPair(pubKey, priKey);
+
+        byte[] commonMac = Hex.decode("0262b12d60690cdcf330baba03188da80eb03090f67cbf2043a18800f4ff0a0262b12d60690cdcf330bab6e69763b471f994dd2d16a5fd82ff1012b6e69763b4");
+
+        //
+        // stream test - V 0
+        //
+        EthereumIESEngine i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        EthereumIESEngine i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        byte[] d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        CipherParameters p = new ParametersWithIV(new IESParameters(d, e, 64), new byte[32]);
+
+        i1.init(true, p1.getPrivate(), p2.getPublic(), p);
+        i2.init(false, p2.getPrivate(), p1.getPublic(), p);
+
+        byte[] message = new byte[0];
+
+        byte[] out1 = i1.processBlock(message, 0, message.length);
+
+        byte[] out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("stream cipher test failed");
+        }
+
+        try
+        {
+            i2.processBlock(out1, 0, out1.length - 1);
+            fail("no exception");
+        }
+        catch (InvalidCipherTextException ex)
+        {
+            if (!"length of input must be greater than the MAC and V combined".equals(ex.getMessage()))
+            {
+                fail("wrong exception");
+            }
+        }
+
+        // with ephemeral key pair
+
+        // Generate the ephemeral key pair
+        ECKeyPairGenerator gen = new ECKeyPairGenerator();
+        gen.init(new ECKeyGenerationParameters(params, new SecureRandom()));
+
+        EphemeralKeyPairGenerator ephKeyGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+        {
+            public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+            {
+                return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(false);
+            }
+        });
+
+        i1.init(p2.getPublic(), p, ephKeyGen);
+        i2.init(p2.getPrivate(), p, new ECIESPublicKeyParser(params));
+
+        out1 = i1.processBlock(message, 0, message.length);
+
+        out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("V cipher test failed");
+        }
+
+        try
+        {
+            i2.processBlock(out1, 0, out1.length - 1);
+            fail("no exception");
+        }
+        catch (InvalidCipherTextException ex)
+        {
+            if (!"length of input must be greater than the MAC and V combined".equals(ex.getMessage()))
+            {
+                fail("wrong exception");
+            }
+        }
+    }
+
+    private void doEphemeralTest(byte[] iv, final boolean usePointCompression)
+        throws Exception
+    {
+        BigInteger n = new BigInteger("6277101735386680763835789423176059013767194773182842284081");
+
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16), // b
+            n, ECConstants.ONE);
+
+        ECDomainParameters params = new ECDomainParameters(
+            curve,
+            curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+            n);
+
+        ECPrivateKeyParameters priKey = new ECPrivateKeyParameters(
+            new BigInteger("651056770906015076056810763456358567190100156695615665659"), // d
+            params);
+
+        ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
+            curve.decodePoint(Hex.decode("0262b12d60690cdcf330babab6e69763b471f994dd702d16a5")), // Q
+            params);
+
+        AsymmetricCipherKeyPair p1 = new AsymmetricCipherKeyPair(pubKey, priKey);
+        AsymmetricCipherKeyPair p2 = new AsymmetricCipherKeyPair(pubKey, priKey);
+
+        // Generate the ephemeral key pair
+        ECKeyPairGenerator gen = new ECKeyPairGenerator();
+        gen.init(new ECKeyGenerationParameters(params, new SecureRandom()));
+
+        EphemeralKeyPairGenerator ephKeyGen = new EphemeralKeyPairGenerator(gen, new KeyEncoder()
+        {
+            public byte[] getEncoded(AsymmetricKeyParameter keyParameter)
+            {
+                return ((ECPublicKeyParameters)keyParameter).getQ().getEncoded(usePointCompression);
+            }
+        });
+
+        byte[] commonMac = Hex.decode("0262b12d60690cdcf330baba03188da80eb03090f67cbf2043a18800f4ff0a0262b12d60690cdcf330bab6e69763b471f994dd2d16a5fd82ff1012b6e69763b4");
+
+        //
+        // stream test
+        //
+        EthereumIESEngine i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        EthereumIESEngine i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+
+        byte[] d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        CipherParameters p = new ParametersWithIV(new IESParameters(d, e, 64), new byte[32]);
+
+        i1.init(p2.getPublic(), p, ephKeyGen);
+        i2.init(p2.getPrivate(), p, new ECIESPublicKeyParser(params));
+
+        byte[] message = Hex.decode("1234567890abcdef");
+
+        byte[] out1 = i1.processBlock(message, 0, message.length);
+
+        byte[] out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("stream cipher test failed");
+        }
+
+        //
+        // twofish with CBC
+        //
+        BufferedBlockCipher c1 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        BufferedBlockCipher c2 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c1);
+        i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c2);
+        d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        p = new IESWithCipherParameters(d, e, 64, 128);
+
+        if (iv != null)
+        {
+            p = new ParametersWithIV(p, iv);
+        }
+
+        i1.init(p2.getPublic(), p, ephKeyGen);
+        i2.init(p2.getPrivate(), p, new ECIESPublicKeyParser(params));
+
+        message = Hex.decode("1234567890abcdef");
+
+        out1 = i1.processBlock(message, 0, message.length);
+
+        out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("twofish cipher test failed");
+        }
+    }
+
+    private void doTest(AsymmetricCipherKeyPair p1, AsymmetricCipherKeyPair p2)
+        throws Exception
+    {
+        byte[] commonMac = Hex.decode("0262b12d60690cdcf330baba03188da80eb03090f67cbf2043a18800f4ff0a0262b12d60690cdcf330bab6e69763b471f994dd2d16a5fd82ff1012b6e69763b4");
+
+        //
+        // stream test
+        //
+        EthereumIESEngine i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        EthereumIESEngine i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac);
+        byte[] d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        byte[] e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        ParametersWithIV p = new ParametersWithIV(new IESParameters(d, e, 64), new byte[32]);
+
+        i1.init(true, p1.getPrivate(), p2.getPublic(), p);
+        i2.init(false, p2.getPrivate(), p1.getPublic(), p);
+
+        byte[] message = Hex.decode("1234567890abcdef");
+
+        byte[] out1 = i1.processBlock(message, 0, message.length);
+
+        byte[] out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("stream cipher test failed");
+        }
+
+        //
+        // twofish with CBC
+        //
+        BufferedBlockCipher c1 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        BufferedBlockCipher c2 = new PaddedBufferedBlockCipher(
+            new CBCBlockCipher(new TwofishEngine()));
+        i1 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c1);
+        i2 = new EthereumIESEngine(
+            new ECDHBasicAgreement(),
+            new EthereumIESEngine.HandshakeKDFFunction(1, new SHA1Digest()),
+            new HMac(new SHA1Digest()),
+            commonMac,
+            c2);
+        d = new byte[]{1, 2, 3, 4, 5, 6, 7, 8};
+        e = new byte[]{8, 7, 6, 5, 4, 3, 2, 1};
+        p = new ParametersWithIV(new IESWithCipherParameters(d, e, 64, 128), new byte[16]);
+
+        i1.init(true, p1.getPrivate(), p2.getPublic(), p);
+        i2.init(false, p2.getPrivate(), p1.getPublic(), p);
+
+        message = Hex.decode("1234567890abcdef");
+
+        out1 = i1.processBlock(message, 0, message.length);
+
+        out2 = i2.processBlock(out1, 0, out1.length);
+
+        if (!areEqual(out2, message))
+        {
+            fail("twofish cipher test failed");
+        }
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        doStaticTest(TWOFISH_IV);
+        doShortTest(null);
+
+        BigInteger n = new BigInteger("6277101735386680763835789423176059013767194773182842284081");
+
+        ECCurve.Fp curve = new ECCurve.Fp(
+            new BigInteger("6277101735386680763835789423207666416083908700390324961279"), // q
+            new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16), // a
+            new BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16), // b
+            n, ECConstants.ONE);
+
+        ECDomainParameters params = new ECDomainParameters(
+            curve,
+            curve.decodePoint(Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012")), // G
+            n);
+
+        ECKeyPairGenerator eGen = new ECKeyPairGenerator();
+        KeyGenerationParameters gParam = new ECKeyGenerationParameters(params, new SecureRandom());
+
+        eGen.init(gParam);
+
+        AsymmetricCipherKeyPair p1 = eGen.generateKeyPair();
+        AsymmetricCipherKeyPair p2 = eGen.generateKeyPair();
+
+        doTest(p1, p2);
+
+        doEphemeralTest(TWOFISH_IV, false);
+        doEphemeralTest(TWOFISH_IV, true);
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new EthereumIESTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/GMacTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/GMacTest.java
index 5c5b515..1a10a8a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/GMacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/GMacTest.java
@@ -12,7 +12,7 @@
 
 /**
  * Test vectors for AES-GMAC, extracted from <a
- * href="http://csrc.nist.gov/groups/STM/cavp/documents/mac/gcmtestvectors.zip">NIST CAVP GCM test
+ * href="https://csrc.nist.gov/groups/STM/cavp/documents/mac/gcmtestvectors.zip">NIST CAVP GCM test
  * vectors</a>.
  *
  */
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/GOST3410Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/GOST3410Test.java
index d1cc6e2..268a47c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/GOST3410Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/GOST3410Test.java
@@ -9,6 +9,7 @@
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.generators.GOST3410KeyPairGenerator;
@@ -39,6 +40,12 @@
 public class GOST3410Test
     implements Test
 {
+    private static ECNamedDomainParameters getECNamedDomainParameters(ASN1ObjectIdentifier oid)
+    {
+        X9ECParameters x9 = ECGOST3410NamedCurves.getByOIDX9(oid);
+        return new ECNamedDomainParameters(oid, x9);
+    }
+
     byte[] hashmessage = Hex.decode("3042453136414534424341374533364339313734453431443642453241453435");
 
     private byte[] zeroTwo(int length)
@@ -104,7 +111,7 @@
             try
             {
                 ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID("GostR3410-2001-CryptoPro-A");
-                ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+                ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
                 ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, CryptoProObjectIdentifiers.gostR3411);
                 ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
                 ECKeyPairGenerator engine = new ECKeyPairGenerator();
@@ -203,7 +210,7 @@
         public SimpleTestResult encodeRecodePublicKeyGost2006()
         {
             ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID("GostR3410-2001-CryptoPro-A");
-            ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+            ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
             ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, CryptoProObjectIdentifiers.gostR3411);
             ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
             ECKeyPairGenerator engine = new ECKeyPairGenerator();
@@ -301,7 +308,7 @@
         {
 
             ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID("Tc26-Gost-3410-12-512-paramSetA");
-            ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+            ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
             ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, RosstandartObjectIdentifiers.id_tc26_gost_3411_12_512);
             ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
             ECKeyPairGenerator engine = new ECKeyPairGenerator();
@@ -404,7 +411,7 @@
             try
             {
                 ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID("Tc26-Gost-3410-12-512-paramSetA");
-                ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+                ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
                 ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, RosstandartObjectIdentifiers.id_tc26_gost_3411_12_512);
                 ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
                 ECKeyPairGenerator engine = new ECKeyPairGenerator();
@@ -664,7 +671,7 @@
             try
             {
                 ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID(oidStr);
-                ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+                ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
                 ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, digest);
                 ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
                 ECKeyPairGenerator engine = new ECKeyPairGenerator();
@@ -757,7 +764,7 @@
             try
             {
                 ASN1ObjectIdentifier oid = ECGOST3410NamedCurves.getOID(oidStr);
-                ECNamedDomainParameters ecp = new ECNamedDomainParameters(oid, ECGOST3410NamedCurves.getByOID(oid));
+                ECNamedDomainParameters ecp = getECNamedDomainParameters(oid);
                 ECGOST3410Parameters gostParams = new ECGOST3410Parameters(ecp, oid, digest);
                 ECKeyGenerationParameters params = new ECKeyGenerationParameters(gostParams, new SecureRandom());
                 ECKeyPairGenerator engine = new ECKeyPairGenerator();
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyTest.java
index 5604d9c..7508332 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyTest.java
@@ -12,8 +12,8 @@
  * HC-128 and HC-256 Tests. Based on the test vectors in the official reference
  * papers, respectively:
  * <pre>
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
  * </pre>
  * See HCFamilyVecTest for a more exhaustive test based on the ecrypt vectors.
  */
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyVecTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyVecTest.java
index 51247e6..fd66f61 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyVecTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/HCFamilyVecTest.java
@@ -19,8 +19,8 @@
  * HC-128 and HC-256 Tests. Based on the test vectors in the official reference
  * papers, respectively:
  * 
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
- * http://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc128_p3.pdf
+ * https://www.ecrypt.eu.org/stream/p3ciphers/hc/hc256_p3.pdf
  */
 public class HCFamilyVecTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka256DigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka256DigestTest.java
new file mode 100644
index 0000000..15dc36b
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka256DigestTest.java
@@ -0,0 +1,186 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.digests.Haraka256Digest;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class Haraka256DigestTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "Haraka 256";
+    }
+
+    public void testKnownVector()
+    {
+        byte[] in = new byte[32];
+        for (int t = 0; t < in.length; t++)
+        {
+            in[t] = (byte)t;
+        }
+
+        // From Appendix B, Haraka-256 v2, https://eprint.iacr.org/2016/098.pdf
+        byte[] expected256 = Hex.decode("8027ccb87949774b78d0545fb72bf70c695c2a0923cbd47bba1159efbf2b2c1c");
+
+        Haraka256Digest haraka = new Haraka256Digest();
+        haraka.update(in, 0, in.length);
+        byte[] out = new byte[haraka.getDigestSize()];
+        haraka.doFinal(out, 0);
+        isTrue("Did not match vector", this.areEqual(expected256, out));
+    }
+
+
+    public void testInputTooShort()
+    {
+        try
+        {
+            Haraka256Digest haraka = new Haraka256Digest();
+            byte[] in = new byte[31];
+            haraka.update(in, 0, in.length);
+            haraka.doFinal(null, 0);
+            fail("fail on input not 32 bytes.");
+        }
+        catch (IllegalStateException ilarex)
+        {
+            isTrue("message", contains(ilarex.getMessage(), "input must be exactly 32 bytes"));
+        }
+    }
+
+    public void testInputTooLong()
+    {
+        try
+        {
+            Haraka256Digest haraka = new Haraka256Digest();
+            byte[] in = new byte[33];
+            haraka.update(in, 0, in.length);
+            haraka.doFinal(null, 0);
+            fail("fail on input not 32 bytes.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("long message", contains(ilarex.getMessage(), "total input cannot be more than 32 bytes"));
+        }
+    }
+
+    public void testOutput()
+    {
+
+        //
+        // Buffer too short.
+        //
+        try
+        {
+            Haraka256Digest haraka = new Haraka256Digest();
+            byte[] in = new byte[32];
+            haraka.update(in, 0, in.length);
+            byte[] out = new byte[31];
+            haraka.doFinal(out, 0);
+            fail("Output too short for digest result.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("message 1", contains(ilarex.getMessage(), "output too short to receive digest"));
+        }
+
+        //
+        // Offset puts end past length of buffer.
+        //
+        try
+        {
+            Haraka256Digest haraka = new Haraka256Digest();
+            byte[] in = new byte[32];
+            haraka.update(in, 0, in.length);
+            byte[] out = new byte[48];
+            haraka.doFinal(out, 17);
+            fail("Output too short for digest result.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("message 2", contains(ilarex.getMessage(), "output too short to receive digest"));
+        }
+
+
+        //
+        // Offset output..
+        //
+        byte[] in = new byte[32];
+        for (int t = 0; t < in.length; t++)
+        {
+            in[t] = (byte)t;
+        }
+
+        byte[] expected256 = Hex.decode("000000008027ccb87949774b78d0545fb72bf70c695c2a0923cbd47bba1159efbf2b2c1c");
+
+        Haraka256Digest haraka = new Haraka256Digest();
+        haraka.update(in, 0, in.length);
+        byte[] out = new byte[haraka.getDigestSize() + 4];
+        haraka.doFinal(out, 4);
+        isTrue(this.areEqual(expected256, out));
+    }
+
+    void testMonty()
+    {
+        int c = 0;
+        String[][] vectors = new String[][]{
+            {
+                "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F",
+                "e78599d7163ab58f1c90f0171c6fc4e852eb4b8cc29a4af63194fd9977c1de84"
+            },
+            {
+                "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+                "c4cebda63c00c4cd312f36ea92afd4b0f6048507c5b367326ef9d8fdd2d5c09a"
+            }
+        };
+
+        for (int i = 0; i != vectors.length; i++)
+        {
+            //
+            // 1000 rounds of digest application, where alternative outputs are copied over alternate halves of the input.
+            //
+            String[] vector = vectors[i];
+
+            byte[] expected = Hex.decode(vector[1]);
+
+            // Load initial message.
+
+            Haraka256Digest haraka = new Haraka256Digest();
+            byte[] result = Hex.decode(vector[0]);
+            for (int t = 0; t < 1000; t++)
+            {
+                haraka.update(result, 0, result.length);
+                haraka.doFinal(result, 0);
+            }
+            isTrue("Monte Carlo test: " + c, this.areEqual(expected, result));
+
+            //
+            // Deliberately introduce incorrect value.
+            //
+
+            result[0] ^= 1;
+            isTrue("Monte Carlo test: " + c, !this.areEqual(expected, result));
+            c++;
+        }
+    }
+
+    private boolean contains(String message, String sub)
+    {
+        return message.indexOf(sub) >= 0;
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testKnownVector();
+        testInputTooLong();
+        testInputTooShort();
+        testOutput();
+        testMonty();
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new Haraka256DigestTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka512DigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka512DigestTest.java
new file mode 100644
index 0000000..0cb4053
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/Haraka512DigestTest.java
@@ -0,0 +1,192 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.digests.Haraka512Digest;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class Haraka512DigestTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "Haraka 512";
+    }
+
+    public void testKnownVector()
+    {
+        byte[] in = new byte[64];
+        for (int t = 0; t < in.length; t++)
+        {
+            in[t] = (byte)t;
+        }
+
+        // From Appendix B, Haraka-512 v2, https://eprint.iacr.org/2016/098.pdf
+        byte[] expected512 = Hex.decode("be7f723b4e80a99813b292287f306f625a6d57331cae5f34dd9277b0945be2aa");
+
+        Haraka512Digest haraka = new Haraka512Digest();
+        haraka.update(in, 0, in.length);
+        byte[] out = new byte[haraka.getDigestSize()];
+        haraka.doFinal(out, 0);
+        isTrue("Did not match vector",this.areEqual(expected512, out));
+    }
+
+    public void testInputTooShort()
+    {
+        try
+        {
+            Haraka512Digest haraka = new Haraka512Digest();
+            byte[] in = new byte[63];
+            haraka.update(in, 0, in.length);
+            haraka.doFinal(null, 0);
+            fail("fail on input not 64 bytes.");
+        }
+        catch (IllegalStateException ilarex)
+        {
+            isTrue("message", contains(ilarex.getMessage(), "input must be exactly 64 bytes"));
+        }
+    }
+
+    public void testInputTooLong()
+    {
+        try
+        {
+            Haraka512Digest haraka = new Haraka512Digest();
+            byte[] in = new byte[65];
+            haraka.update(in, 0, in.length);
+            haraka.doFinal(null, 0);
+            fail("fail on input not 64 bytes.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("message", contains(ilarex.getMessage(), "total input cannot be more than 64 bytes"));
+        }
+    }
+
+    public void testOutput()
+    {
+        //
+        // Buffer too short.
+        //
+        try
+        {
+            Haraka512Digest haraka = new Haraka512Digest();
+            byte[] in = new byte[64];
+            haraka.update(in, 0, in.length);
+            byte[] out = new byte[31];
+            haraka.doFinal(out, 0);
+            fail("Output too short for digest result.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("message", contains(ilarex.getMessage(), "output too short to receive digest"));
+        }
+
+        //
+        // Offset puts end past length of buffer.
+        //
+        try
+        {
+            Haraka512Digest haraka = new Haraka512Digest();
+            byte[] in = new byte[64];
+            haraka.update(in, 0, in.length);
+            byte[] out = new byte[48];
+            haraka.doFinal(out, 17);
+            fail("Output too short for digest result.");
+        }
+        catch (IllegalArgumentException ilarex)
+        {
+            isTrue("message", contains(ilarex.getMessage(), "output too short to receive digest"));
+        }
+
+        //
+        // Offset output..
+        //
+        byte[] in = new byte[64];
+        for (int t = 0; t < in.length; t++)
+        {
+            in[t] = (byte)t;
+        }
+
+        byte[] expected512 = Hex.decode("00000000be7f723b4e80a99813b292287f306f625a6d57331cae5f34dd9277b0945be2aa");
+
+        Haraka512Digest haraka = new Haraka512Digest();
+        haraka.update(in, 0, in.length);
+        byte[] out = new byte[haraka.getDigestSize() + 4];
+        haraka.doFinal(out, 4);
+        isTrue(this.areEqual(expected512, out));
+    }
+
+    void testMonty()
+    {
+        int c = 0;
+        String[][] vectors = new String[][]{
+            {
+                "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F",
+                "ABE210FE673F3B28E70E5100C476D82F61A7E2BDB3D8423FB0A15E5DE3D3A4DE"
+            },
+            {
+                "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
+                "5F5ECB52C61F5036C96BE555D2E18C520AB3ED093954700C283A322D14DBFE02"
+            }
+        };
+
+        for (int i = 0; i != vectors.length; i++)
+        {
+            //
+            // 1000 rounds of digest application, where alternative outputs are copied over alternate halves of the input.
+            //
+            String[] vector = vectors[i];
+            
+            byte[] expected = Hex.decode(vector[1]);
+
+            // Load initial message.
+            byte[] in = Hex.decode(vector[0]);
+            Haraka512Digest haraka = new Haraka512Digest();
+            byte[] result = new byte[haraka.getDigestSize()];
+            for (int t = 0; t < 1000; t++)
+            {
+                haraka.update(in, 0, in.length);
+                haraka.doFinal(result, 0);
+
+                if ((t & 0x01) == 1)
+                {
+                    System.arraycopy(result, 0, in, 0, result.length);
+                }
+                else
+                {
+                    System.arraycopy(result, 0, in, result.length, result.length);
+                }
+            }
+            isTrue("Monte Carlo test: " + c, this.areEqual(expected, result));
+
+            //
+            // Deliberately introduce incorrect value.
+            //
+
+            result[0] ^= 1;
+            isTrue("Monte Carlo test: " + c, !this.areEqual(expected, result));
+            c++;
+        }
+    }
+
+    private boolean contains(String message, String sub)
+    {
+        return message.indexOf(sub) >= 0;
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        testKnownVector();
+        testInputTooLong();
+        testInputTooShort();
+        testOutput();
+        testMonty();
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new Haraka512DigestTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ISAACTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ISAACTest.java
index 02319a3..7a940af 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/ISAACTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ISAACTest.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * ISAAC Test - see http://www.burtleburtle.net/bob/rand/isaacafa.html
+ * ISAAC Test - see https://www.burtleburtle.net/bob/rand/isaacafa.html
  */
 public class ISAACTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/KMACTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/KMACTest.java
new file mode 100644
index 0000000..52f9fb7
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/KMACTest.java
@@ -0,0 +1,257 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.macs.KMAC;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * KMAC test vectors from:
+ * <p>
+ * https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf
+ */
+public class KMACTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "KMAC";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        KMAC kmac = new KMAC(128, Strings.toByteArray(""));
+
+        isEquals("KMAC128", kmac.getAlgorithmName());
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        kmac.update(Hex.decode("00010203"), 0, 4);
+
+        byte[] res = new byte[32];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops: " + Hex.toHexString(res), Arrays.areEqual(Hex.decode("E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E"), res));
+
+        kmac = new KMAC(128, Strings.toByteArray("My Tagged Application"));
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        kmac.update(Hex.decode("00010203"), 0, 4);
+
+        res = new byte[32];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops: " + Hex.toHexString(res), Arrays.areEqual(Hex.decode("3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5"), res));
+
+        kmac = new KMAC(128, Strings.toByteArray("My Tagged Application"));
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        byte[] data = Hex.decode(
+            "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1" +
+                "F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3" +
+                "E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5" +
+                "D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7" +
+                "C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9" +
+                "B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9B" +
+                "ABBBCBDBEBFC0C1C2C3C4C5C6C7");
+        kmac.update(data, 0, data.length);
+
+        res = new byte[32];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops:" + Hex.toHexString(res), Arrays.areEqual(Hex.decode("1F5B4E6CCA02209E0DCB5CA635B89A15E271ECC760071DFD805FAA38F9729230"), res));
+
+        kmac = new KMAC(256, Strings.toByteArray("My Tagged Application"));
+
+        isEquals("KMAC256", kmac.getAlgorithmName());
+
+        kmac.init(new KeyParameter(Hex.decode("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        data = Hex.decode("00 01 02 03");
+        kmac.update(data, 0, data.length);
+
+        res = new byte[64];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops:" + Hex.toHexString(res), Arrays.areEqual(Hex.decode("20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD"), res));
+
+        kmac = new KMAC(256, Strings.toByteArray(""));
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        data = Hex.decode(
+            "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1" +
+                "F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3" +
+                "E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5" +
+                "D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7" +
+                "C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9" +
+                "B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9B" +
+                "ABBBCBDBEBFC0C1C2C3C4C5C6C7");
+        kmac.update(data, 0, data.length);
+
+        res = new byte[64];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops:" + Hex.toHexString(res), Arrays.areEqual(Hex.decode("75358CF39E41494E949707927CEE0AF20A3FF553904C86B08F21CC414BCFD691589D27CF5E15369CBBFF8B9A4C2EB17800855D0235FF635DA82533EC6B759B69"), res));
+
+        kmac = new KMAC(256, Strings.toByteArray("My Tagged Application"));
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+
+        data = Hex.decode(
+            "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1" +
+                "F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3" +
+                "E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5" +
+                "D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7" +
+                "C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9" +
+                "B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9B" +
+                "ABBBCBDBEBFC0C1C2C3C4C5C6C7");
+        kmac.update(data, 0, data.length);
+
+        res = new byte[64];
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue("oops:" + Hex.toHexString(res), Arrays.areEqual(Hex.decode("B58618F71F92E1D56C1B8C55DDD7CD188B97B4CA4D99831EB2699A837DA2E4D970FBACFDE50033AEA585F1A2708510C32D07880801BD182898FE476876FC8965"), res));
+
+        doFinalTest();
+        longBlockTest();
+        paddingCheckTest();
+
+        checkKMAC(128, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
+        checkKMAC(256, new KMAC(256, null), Hex.decode("eeaabeef"));
+        checkKMAC(128, new KMAC(128, new byte[0]), Hex.decode("eeaabeef"));
+        checkKMAC(128, new KMAC(128, null), Hex.decode("eeaabeef"));
+        checkKMAC(256, new KMAC(256,  null), Hex.decode("eeaabeef"));
+    }
+
+    private void doFinalTest()
+    {
+        KMAC kmac = new KMAC(128, Strings.toByteArray("My Tagged Application"));
+
+        kmac.init(new KeyParameter(Hex.decode(
+            "404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F")));
+        
+        kmac.update(Hex.decode("00010203"), 0, 4);
+
+        byte[] res = new byte[32];
+
+        kmac.doOutput(res, 0, res.length);
+
+        isTrue(Hex.toHexString(res), Arrays.areEqual(Hex.decode("31a44527b4ed9f5c6101d11de6d26f0620aa5c341def41299657fe9df1a3b16c"), res));
+
+        kmac.doOutput(res, 0, res.length);
+
+        isTrue(!Arrays.areEqual(Hex.decode("31a44527b4ed9f5c6101d11de6d26f0620aa5c341def41299657fe9df1a3b16c"), res));
+
+        kmac.doFinal(res, 0, res.length);
+
+        kmac.update(Hex.decode("00010203"), 0, 4);
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue(Arrays.areEqual(Hex.decode("3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5"), res));
+
+        kmac.update(Hex.decode("00010203"), 0, 4);
+
+        kmac.doOutput(res, 0, res.length);
+
+        isTrue(Arrays.areEqual(Hex.decode("31a44527b4ed9f5c6101d11de6d26f0620aa5c341def41299657fe9df1a3b16c"), res));
+
+        kmac.doFinal(res, 0, res.length);
+
+        isTrue(Hex.toHexString(res), Arrays.areEqual(Hex.decode("ffcb48c7620ccd67d1c83224186892cef2f2a99278d5cfdde10e48bdc89718c2"), res));
+    }
+
+    private void longBlockTest()
+    {
+        byte[] data = new byte[16000];
+        byte[] res = new byte[64];
+
+        for (int i = 0; i != data.length; i++)
+        {
+            data[i] = (byte)i;
+        }
+
+        for (int i = 10000; i != data.length; i++)
+        {
+            KMAC kmac = new KMAC(128, Arrays.copyOfRange(data, 0, i));
+
+            kmac.init(new KeyParameter(new byte[0]));
+
+            kmac.update(Hex.decode("00010203"), 0, 4);
+
+            kmac.doFinal(res, 0);
+        }
+
+        KMAC kmac = new KMAC(256, new byte[200]);
+
+        kmac.init(new KeyParameter(new byte[0]));
+
+        kmac.update(Arrays.copyOfRange(data, 0, 200), 0, 200);
+
+        kmac.doFinal(res, 0);
+
+        isTrue(Hex.toHexString(res), Arrays.areEqual(Hex.decode("f9476d9b3e42bf23307af5ccb5287fd6f033b23c400566a2ebc5829bd119aa545cd9b6bde76ef61cd31c3c0f0aaf0945f44481e863b19e9c26fb46c8b2a8a9bb"), res));
+    }
+
+    private void paddingCheckTest()
+    {
+        byte[] data = Hex.decode("01880204187B3E43EDA8D51EC181D37DDE5B17ECCDD8BE84C268DC6C9500700857");
+        byte[] out = new byte[32];
+
+        KMAC k128 = new KMAC(128, new byte[0]);
+        k128.init(new KeyParameter(new byte[163]));
+        k128.update(data, 0, data.length);
+        k128.doOutput(out, 0, out.length);
+
+        isTrue("128 failed", Arrays.areEqual(out, Hex.decode("6e6ab56468c7445f81c679f89f45c90a95a9c01afbaab5f7065b7e2e96f7d2bb")));
+
+        KMAC k256 = new KMAC(256, new byte[0]);
+        k256.init(new KeyParameter(new byte[131]));
+        k256.update(data, 0, data.length);
+        k256.doOutput(out, 0, out.length);
+
+        isTrue("256 failed", Arrays.areEqual(out, Hex.decode("f6302d4f854b4872e811b37993b6bfe027258089b6a9fbb26a755b1ebfc0d830")));
+    }
+
+    private void checkKMAC(int bitSize, KMAC kmac, byte[] msg)
+    {
+        KMAC ref = new KMAC(bitSize, null);
+
+        ref.init(new KeyParameter(new byte[0]));
+        kmac.init(new KeyParameter(new byte[0]));
+        
+        ref.update(msg, 0, msg.length);
+        kmac.update(msg, 0, msg.length);
+
+        byte[] res1 = new byte[32];
+        byte[] res2 = new byte[32];
+
+        ref.doFinal(res1, 0, res1.length);
+        kmac.doFinal(res2, 0, res2.length);
+
+        isTrue(Arrays.areEqual(res1, res2));
+    }
+
+    public static void main(
+        String[] args)
+    {
+        runTest(new KMACTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java
index fc566d3..22d6146 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/KeccakDigestTest.java
@@ -66,7 +66,7 @@
         "3e122edaf37398231cfaca4c7c216c9d66d5b899ec1d7ac617c40c7261906a45fc01617a021e5da3bd8d4182695b5cb785a28237cbb167590e34718e56d8aab8"
     };
 
-    // test vectors from  http://www.di-mgt.com.au/hmac_sha3_testvectors.html
+    // test vectors from  https://www.di-mgt.com.au/hmac_sha3_testvectors.html
     final static byte[][] macKeys =
     {
         Hex.decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/MacTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/MacTest.java
index 5b05a60..d5e26c2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/MacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/MacTest.java
@@ -5,16 +5,16 @@
 import org.bouncycastle.crypto.engines.DESEngine;
 import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
 import org.bouncycastle.crypto.macs.CFBBlockCipherMac;
+import org.bouncycastle.crypto.paddings.PKCS7Padding;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
-import org.bouncycastle.crypto.paddings.PKCS7Padding;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
  * MAC tester - vectors from 
- * <a href=http://www.itl.nist.gov/fipspubs/fip81.htm>FIP 81</a> and 
- * <a href=http://www.itl.nist.gov/fipspubs/fip113.htm>FIP 113</a>.
+ * <a href=https://www.itl.nist.gov/fipspubs/fip81.htm>FIP 81</a> and 
+ * <a href=https://www.itl.nist.gov/fipspubs/fip113.htm>FIP 113</a>.
  */
 public class MacTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/NISTECCTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/NISTECCTest.java
new file mode 100644
index 0000000..c65b834
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/NISTECCTest.java
@@ -0,0 +1,120 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.ec.CustomNamedCurves;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.test.SimpleTest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class NISTECCTest
+    extends SimpleTest
+{
+    public String getName()
+    {
+        return "NISTECC";
+    }
+
+    public void performTest() throws Exception
+    {
+        testVectors();
+    }
+
+    public void testVectors() throws IOException
+    {
+        BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("nist_ecc.txt")));
+        try
+        {
+            String line = br.readLine();
+            X9ECParameters curve = null;
+            X9ECParameters curveCustom = null;
+            BigInteger k = null;
+            BigInteger x = null;
+            BigInteger y = null;
+
+            while (line != null)
+            {
+                while (null != (line = br.readLine()))
+                {
+                    Matcher matcher = Pattern.compile("^ ?(\\w+):? =? ?(\\w+)").matcher(line);
+                    if (!matcher.matches())
+                    {
+                        continue;
+                    }
+
+                    String nistKey = matcher.group(1);
+                    String nistValue = matcher.group(2);
+
+                    if ("Curve".equals(nistKey))
+                    {
+                        // Change curve name from LNNN to L-NNN ie: P256 to P-256
+                        String curveName = nistValue.charAt(0) + "-" + nistValue.substring(1);
+                        curve = NISTNamedCurves.getByName(curveName);
+                        curveCustom = CustomNamedCurves.getByName(curveName);
+                    }
+                    else if ("k".equals(nistKey))
+                    {
+                        k = new BigInteger(nistValue, 10);
+                    }
+                    else if ("x".equals(nistKey))
+                    {
+                        x = new BigInteger(nistValue, 16);
+                    }
+                    else if ("y".equals(nistKey))
+                    {
+                        y = new BigInteger(nistValue, 16);
+                    }
+
+                    if (k == null || x == null || y == null)
+                    {
+                        continue;
+                    }
+
+                    if (curve != null)
+                    {
+                        implTestMultiply(curve, k, x, y);
+                    }
+                    if (curveCustom != null)
+                    {
+                        implTestMultiply(curveCustom, k, x, y);
+                    }
+
+                    k = null;
+                    x = null;
+                    y = null;
+                }
+            }
+        }
+        catch (IOException exception)
+        {
+            fail("Failed to load resources.", exception);
+        }
+        finally
+        {
+            br.close();
+        }
+    }
+
+    private void implTestMultiply(X9ECParameters curve, BigInteger k, BigInteger x, BigInteger y)
+    {
+        // Act
+        ECPoint ecPoint = curve.getG().multiply(k).normalize();
+        BigInteger affineXCoord = ecPoint.getAffineXCoord().toBigInteger();
+        BigInteger affineYCoord = ecPoint.getAffineYCoord().toBigInteger();
+
+        // Assert
+        isEquals("Unexpected X Coordinate", x, affineXCoord);
+        isEquals("Unexpected Y Coordinate", y, affineYCoord);
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new NISTECCTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/NaccacheSternTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/NaccacheSternTest.java
index 56e0e30..f85b231 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/NaccacheSternTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/NaccacheSternTest.java
@@ -17,7 +17,7 @@
 /**
  * Test case for NaccacheStern cipher. For details on this cipher, please see
  * 
- * http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
+ * https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf
  *
  * Performs the following tests: 
  *  <ul>
@@ -91,7 +91,7 @@
         decryptEng.setDebug(debug);
 
         // First the Parameters from the NaccacheStern Paper
-        // (see http://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf )
+        // (see https://www.gemplus.com/smart/rd/publications/pdf/NS98pkcs.pdf )
 
         smallPrimes.addElement(u1);
         smallPrimes.addElement(u2);
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/OCBTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/OCBTest.java
index 26d7d12..7c1e642 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/OCBTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/OCBTest.java
@@ -16,7 +16,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * Test vectors from <a href="http://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
+ * Test vectors from <a href="https://tools.ietf.org/html/rfc7253">RFC 7253 on The OCB
  * Authenticated-Encryption Algorithm</a>
  */
 public class OCBTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenBSDBCryptTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenBSDBCryptTest.java
index 679f4e5..f42cc51 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenBSDBCryptTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenBSDBCryptTest.java
@@ -1,6 +1,11 @@
 package org.bouncycastle.crypto.test;
 
+import java.security.SecureRandom;
+import java.util.ArrayList;
+
 import org.bouncycastle.crypto.generators.OpenBSDBCrypt;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.test.SimpleTest;
 
 public class OpenBSDBCryptTest
@@ -28,7 +33,7 @@
     };
 
     private final static String[] bcryptTest2b = { // from: http://stackoverflow.com/questions/11654684/verifying-a-bcrypt-hash
-        "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle", "ππππππππ"
+        "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle", "\u03c0\u03c0\u03c0\u03c0\u03c0\u03c0\u03c0\u03c0"
     };
 
     private final static String[][] bcryptTest3 = // from: https://bitbucket.org/vadim/bcrypt.net/src/464c41416dc9/BCrypt.Net.Test/TestBCrypt.cs - plain - salt - expected
@@ -91,6 +96,20 @@
         {"ABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXY", "$2b$12$QwAt5kuG68nW7v.87q0QPuwdki3romFc/RU/RV3Qqk4FPw6WdbQzu"}
     };
 
+    private static final String[][] twoVec = new String[][]{
+        {"a", "$2$12$DB3BUbYa/SsEL7kCOVji0OauTkPkB5Y1OeyfxJHM7jvMrbml5sgD2"},
+        {"abc", "$2$12$p.xODEbFcXUlHGbNxWZqAe6AA5FWupqXmN9tZea2ACDhwIx4EA2a6"},
+        {"hello world", "$2$12$wfkxITYXjNLVpEi9nOjz7uXMhCXKSTY7O2y7X4bwY89aGSvRziguq"},
+        {"ABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXY", "$2$12$QwAt5kuG68nW7v.87q0QPuwdki3romFc/RU/RV3Qqk4FPw6WdbQzu"}
+    };
+
+    private static final String[][] twoXVec = new String[][]{
+        {"a", "$2x$12$DB3BUbYa/SsEL7kCOVji0OauTkPkB5Y1OeyfxJHM7jvMrbml5sgD2"},
+        {"abc", "$2x$12$p.xODEbFcXUlHGbNxWZqAe6AA5FWupqXmN9tZea2ACDhwIx4EA2a6"},
+        {"hello world", "$2x$12$wfkxITYXjNLVpEi9nOjz7uXMhCXKSTY7O2y7X4bwY89aGSvRziguq"},
+        {"ABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXYABCDEFGHIJKLMNOPQRSTUVWXY", "$2x$12$QwAt5kuG68nW7v.87q0QPuwdki3romFc/RU/RV3Qqk4FPw6WdbQzu"}
+    };
+
     public static void main(String[] args)
     {
         runTest(new OpenBSDBCryptTest());
@@ -101,9 +120,127 @@
         return "OpenBSDBCrypt";
     }
 
+
+    public void testPermutations()
+        throws Exception
+    {
+
+        byte[] rootPassword = Strings.toByteArray("aabcc");
+        byte[] buf = null;
+
+        byte[][] salts = new byte[3][];
+
+        salts[0] = new byte[16];
+        salts[1] = new byte[16];
+        salts[2] = new byte[16];
+        for (int t = 0; t < 16; t++)
+        {
+            salts[1][t] = (byte)t;
+            salts[2][t] = (byte)(16 - t);
+        }
+
+
+
+        //
+        // Permutation, starting with a shorter array, same length then one longer.
+        //
+        for (int j = rootPassword.length - 1; j < rootPassword.length + 2; j++)
+        {
+            buf = new byte[j];
+
+            for (int a = 0; a < rootPassword.length; a++)
+            {
+                for (int b = 0; b < buf.length; b++)
+                {
+                    buf[b] = rootPassword[(a + b) % rootPassword.length];
+                }
+
+
+                ArrayList<byte[]> permutations = new ArrayList<byte[]>();
+                permute(permutations, buf, 0, buf.length - 1);
+
+                for (int i = 0; i != permutations.size(); i++)
+                {
+                    byte[] candidate = (byte[])permutations.get(i);
+                    for (int k = 0; k != salts.length; k++)
+                    {
+                        byte[] salt = salts[k];
+                        String expected = OpenBSDBCrypt.generate(rootPassword, salt, 4);
+                        String testValue = OpenBSDBCrypt.generate(candidate, salt, 4);
+
+                        //
+                        // If the passwords are the same for the same salt we should have the same string.
+                        //
+                        boolean sameAsRoot = Arrays.areEqual(rootPassword, candidate);
+                        isTrue("expected same result", sameAsRoot == expected.equals(testValue));
+
+                        //
+                        // check that the test password passes against itself.
+                        //
+                        isTrue("candidate valid with self", OpenBSDBCrypt.checkPassword(testValue, candidate));
+
+                        //
+                        // Check against expected, it should always track sameAsRoot.
+                        //
+                        boolean candidateRootValid = OpenBSDBCrypt.checkPassword(expected, candidate);
+                        isTrue("candidate valid with root", sameAsRoot == candidateRootValid);
+                    }
+
+                }
+            }
+        }
+    }
+
+
+    private void swap(byte[] buf, int i, int j)
+    {
+        byte b = buf[i];
+        buf[i] = buf[j];
+        buf[j] = b;
+    }
+
+    private void permute(ArrayList<byte[]> permutation, byte[] a, int l, int r)
+    {
+        if (l == r)
+        {
+            permutation.add(Arrays.clone(a));
+        }
+        else
+        {
+
+            for (int i = l; i <= r; i++)
+            {
+                // Swapping done
+                swap(a, l, i);
+
+                // Recursion called
+                permute(permutation, a, l + 1, r);
+
+                //backtrack
+                swap(a, l, i);
+            }
+        }
+    }
+
+
     public void performTest()
         throws Exception
     {
+
+        testPermutations();
+
+        byte[] salt = new byte[16];
+        for (int i = 0; i < bcryptTest1.length; i++)
+        {
+            String[] testString = bcryptTest1[i];
+            char[] password = testString[1].toCharArray();
+
+            String s1 = OpenBSDBCrypt.generate(password, salt, 4);
+            String s2 = OpenBSDBCrypt.generate(Strings.toByteArray(password), salt, 4);
+
+            isEquals(s1, s2);
+        }
+
         for (int i = 0; i < bcryptTest1.length; i++)
         {
             String[] testString = bcryptTest1[i];
@@ -142,7 +279,6 @@
             }
         }
 
-
         for (int i = 0; i < bcryptTest4.length; i++)
         {
             String[] testString = bcryptTest4[i];
@@ -165,16 +301,51 @@
             }
         }
 
-        for (int i = 0; i < twoBVec.length; i++)
+        oldPrefixTest(twoBVec);
+
+        oldPrefixTest(twoXVec);
+
+        oldPrefixTest(twoVec);
+
+        int costFactor = 4;
+        SecureRandom random = new SecureRandom();
+        salt = new byte[16];
+        for (int i = 0; i < 1000; i++)
         {
-            password = twoBVec[i][0];
-            encoded = twoBVec[i][1];
+            random.nextBytes(salt);
+            final String tokenString = OpenBSDBCrypt
+                .generate("test-token".toCharArray(), salt, costFactor);
+
+            isTrue(OpenBSDBCrypt.checkPassword(tokenString, "test-token".toCharArray()));
+            isTrue(!OpenBSDBCrypt.checkPassword(tokenString, "wrong-token".toCharArray()));
+        }
+    }
+
+    private void oldPrefixTest(String[][] twoXVec)
+    {
+        String password;
+        String encoded;
+        for (int i = 0; i < twoXVec.length; i++)
+        {
+            password = twoXVec[i][0];
+            encoded = twoXVec[i][1];
 
             if (!OpenBSDBCrypt.checkPassword(encoded, password.toCharArray()))
             {
                 fail("twoBVec mismatch: " + "[" + i + "] " + password);
             }
         }
+
+        for (int i = 0; i < twoXVec.length; i++)
+        {
+            password = twoXVec[i][0];
+            encoded = twoXVec[i][1];
+
+            if (!OpenBSDBCrypt.checkPassword(encoded, Strings.toUTF8ByteArray(password)))
+            {
+                fail("twoBVec mismatch: " + "[" + i + "] " + password);
+            }
+        }
     }
 }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenSSHKeyParsingTests.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenSSHKeyParsingTests.java
index c4c9364..fa2be55 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenSSHKeyParsingTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/OpenSSHKeyParsingTests.java
@@ -6,6 +6,7 @@
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.engines.RSAEngine;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.signers.DSASigner;
 import org.bouncycastle.crypto.signers.ECDSASigner;
 import org.bouncycastle.crypto.signers.Ed25519Signer;
@@ -62,6 +63,101 @@
     }
 
 
+    public void testECDSA_curvesFromSSHKeyGen()
+        throws Exception
+    {
+
+        String[][] pairs = new String[][]{
+            {
+                "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmwH7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPiI=",
+                "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQW8ShPvw17LUGZsB+1rKPElLfC72KD\n" +
+                    "EKcTf+yDQtJTJ+5jQm8SXEjgn7nAbljq3aBchWVJBpYtpsyo94SZXD4iAAAAuKFclDShXJ\n" +
+                    "Q0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBbxKE+/DXstQZmw\n" +
+                    "H7Wso8SUt8LvYoMQpxN/7INC0lMn7mNCbxJcSOCfucBuWOrdoFyFZUkGli2mzKj3hJlcPi\n" +
+                    "IAAAAhAP4L/ciGBDF4HoQSvMaKM8svW4Ss0uYi7HkZ1sn/zCe0AAAAHW1lZ2Fud29vZHNA\n" +
+                    "dHljaGUtMzI2NS5nYXRld2F5AQI=\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+            },
+            {
+                "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOT0Cc/zauJsOWo/0P0sMNeyFI5Enz3+lKJtjWXQD7DpFgZmG5Ise8IXR5/ot7fo0kWlYQrye/uSmNmWBuDvOpBCHOnyR6Kaej36qoOO/gwbH+mezSYXSxCTA9Qb8VzxLA==",
+                "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTk9AnP82ribDlqP9D9LDDXshSORJ89\n" +
+                    "/pSibY1l0A+w6RYGZhuSLHvCF0ef6Le36NJFpWEK8nv7kpjZlgbg7zqQQhzp8keimno9+q\n" +
+                    "qDjv4MGx/pns0mF0sQkwPUG/Fc8SwAAADorZ3naK2d52gAAAATZWNkc2Etc2hhMi1uaXN0\n" +
+                    "cDM4NAAAAAhuaXN0cDM4NAAAAGEE5PQJz/Nq4mw5aj/Q/Sww17IUjkSfPf6Uom2NZdAPsO\n" +
+                    "kWBmYbkix7whdHn+i3t+jSRaVhCvJ7+5KY2ZYG4O86kEIc6fJHopp6Pfqqg47+DBsf6Z7N\n" +
+                    "JhdLEJMD1BvxXPEsAAAAMQDLno+rINnY7/Ht1WmSGZYJ3EMPtysbxuBnQFEL4USa3kyAb1\n" +
+                    "QMR6+jtqraKtE7kLwAAAAdbWVnYW53b29kc0B0eWNoZS0zMjY1LmdhdGV3YXkBAg==\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+            },
+            {
+                "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADXE/q1WSR002vRI+tiPLpdRjzeymSk+RjD7ZIC9CndqLmI0rhTMh5xReAzved12BH9lQJIGIw4YoIQDudsMbRUsQEjFvbFzSXLJBYWdZf8Voa/97/R9w/i8bKUMUPP0disypZlGdQn5+XvzHG6bhX2Qr9aJacGFZoVHugF/M8QyC+GyA==",
+                "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+                    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS\n" +
+                    "1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA1xP6tVkkdNNr0SPrYjy6XUY83spk\n" +
+                    "pPkYw+2SAvQp3ai5iNK4UzIecUXgM73nddgR/ZUCSBiMOGKCEA7nbDG0VLEBIxb2xc0lyy\n" +
+                    "QWFnWX/FaGv/e/0fcP4vGylDFDz9HYrMqWZRnUJ+fl78xxum4V9kK/WiWnBhWaFR7oBfzP\n" +
+                    "EMgvhsgAAAEgs+rbdbPq23UAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ\n" +
+                    "AAAIUEANcT+rVZJHTTa9Ej62I8ul1GPN7KZKT5GMPtkgL0Kd2ouYjSuFMyHnFF4DO953XY\n" +
+                    "Ef2VAkgYjDhighAO52wxtFSxASMW9sXNJcskFhZ1l/xWhr/3v9H3D+LxspQxQ8/R2KzKlm\n" +
+                    "UZ1Cfn5e/McbpuFfZCv1olpwYVmhUe6AX8zxDIL4bIAAAAQgCM8ojULpNk3UhBZhPfK+Tw\n" +
+                    "QjT9MHU0OTi4twvKPAE0vOLQ/C1g9AMlspyKxS2NKx2gxxXISowFGNL6Jkx9198ElQAAAB\n" +
+                    "1tZWdhbndvb2RzQHR5Y2hlLTMyNjUuZ2F0ZXdheQECAwQF\n" +
+                    "-----END OPENSSH PRIVATE KEY-----\n"
+            }
+        };
+
+
+        for (int i = 0; i != pairs.length; i++)
+        {
+            String[] pair = pairs[i];
+
+            CipherParameters pubSpec = OpenSSHPublicKeyUtil.parsePublicKey(
+                Base64.decode(pair[0]));
+
+            CipherParameters privSpec = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(
+                new PemReader(
+                    new StringReader(pair[1])).readPemObject().getContent());
+
+            ECDSASigner signer = new ECDSASigner();
+            signer.init(true, privSpec);
+
+            byte[] originalMessage = new byte[10];
+            secureRandom.nextBytes(originalMessage);
+
+            BigInteger[] rs = signer.generateSignature(originalMessage);
+
+            signer.init(false, pubSpec);
+
+            isTrue("ECDSA test", signer.verifySignature(originalMessage, rs[0], rs[1]));
+
+            //
+            // Test encode
+            //
+
+
+            CipherParameters recoveredPubKey = OpenSSHPublicKeyUtil.parsePublicKey(OpenSSHPublicKeyUtil.encodePublicKey((AsymmetricKeyParameter)pubSpec));
+            CipherParameters recoveredPrivateKey = OpenSSHPrivateKeyUtil.parsePrivateKeyBlob(OpenSSHPrivateKeyUtil.encodePrivateKey((AsymmetricKeyParameter)privSpec));
+
+            signer = new ECDSASigner();
+            signer.init(true, privSpec);
+
+            originalMessage = new byte[10];
+            secureRandom.nextBytes(originalMessage);
+
+            rs = signer.generateSignature(originalMessage);
+
+            signer.init(false, pubSpec);
+
+            isTrue("ECDSA test post encoded / decode", signer.verifySignature(originalMessage, rs[0], rs[1]));
+        }
+
+    }
+
+
     public void testECDSA()
         throws Exception
     {
@@ -185,6 +281,7 @@
     public void performTest()
         throws Exception
     {
+        testECDSA_curvesFromSSHKeyGen();
         testDSA();
         testECDSA();
         testRSA();
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS12Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS12Test.java
index c5c7aa3..a5402ae 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS12Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS12Test.java
@@ -13,8 +13,8 @@
 
 /**
  * test for PKCS12 key generation - vectors from 
- * <a href=http://www.drh-consultancy.demon.co.uk/test.txt>
- * http://www.drh-consultancy.demon.co.uk/test.txt</a>
+ * <a href=https://www.drh-consultancy.demon.co.uk/test.txt>
+ * https://www.drh-consultancy.demon.co.uk/test.txt</a>
  */
 public class PKCS12Test
     implements Test
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS5Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS5Test.java
index 6145114..994d020 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS5Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/PKCS5Test.java
@@ -6,7 +6,6 @@
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.EncryptionScheme;
-import org.bouncycastle.asn1.pkcs.KeyDerivationFunc;
 import org.bouncycastle.asn1.pkcs.PBES2Parameters;
 import org.bouncycastle.asn1.pkcs.PBKDF2Params;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -29,7 +28,7 @@
 /**
  * A test class for PKCS5 PBES2 with PBKDF2 (PKCS5 v2.0) using
  * test vectors provider at 
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-5/index.html>
  * RSA's PKCS5 Page</a>
  * <br>
  * The vectors are Base 64 encoded and encrypted using the password "password"
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSABlindedTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSABlindedTest.java
index c7efcae..27a9aeb 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSABlindedTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSABlindedTest.java
@@ -145,7 +145,7 @@
         {
             data = eng.processBlock(data, 0, data.length);
 
-            fail("missing data block not recognised");
+            fail("incorrect block not recognised");
         }
         catch (InvalidCipherTextException e)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSADigestSignerTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSADigestSignerTest.java
index ab1392d..799966c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSADigestSignerTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSADigestSignerTest.java
@@ -3,9 +3,14 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.DigestInfo;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.crypto.CryptoException;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
 import org.bouncycastle.crypto.digests.SHA224Digest;
 import org.bouncycastle.crypto.digests.SHA256Digest;
@@ -42,9 +47,11 @@
         RSAPrivateCrtKeyParameters rsaPrivate = new RSAPrivateCrtKeyParameters(rsaPrivMod, rsaPubExp, rsaPrivExp, rsaPrivP, rsaPrivQ, rsaPrivDP, rsaPrivDQ, rsaPrivQinv);
 
         checkDigest(rsaPublic, rsaPrivate, new SHA1Digest(), X509ObjectIdentifiers.id_SHA1);
+        checkNullDigest(rsaPublic, rsaPrivate, new SHA1Digest(), X509ObjectIdentifiers.id_SHA1);
 
         checkDigest(rsaPublic, rsaPrivate, new SHA224Digest(), NISTObjectIdentifiers.id_sha224);
         checkDigest(rsaPublic, rsaPrivate, new SHA256Digest(), NISTObjectIdentifiers.id_sha256);
+        checkNullDigest(rsaPublic, rsaPrivate, new SHA256Digest(), NISTObjectIdentifiers.id_sha256);
         checkDigest(rsaPublic, rsaPrivate, new SHA384Digest(), NISTObjectIdentifiers.id_sha384);
         checkDigest(rsaPublic, rsaPrivate, new SHA512Digest(), NISTObjectIdentifiers.id_sha512);
         checkDigest(rsaPublic, rsaPrivate, new SHA512tDigest(224), NISTObjectIdentifiers.id_sha512_224);
@@ -54,6 +61,23 @@
         checkDigest(rsaPublic, rsaPrivate, new SHA3Digest(256), NISTObjectIdentifiers.id_sha3_256);
         checkDigest(rsaPublic, rsaPrivate, new SHA3Digest(384), NISTObjectIdentifiers.id_sha3_384);
         checkDigest(rsaPublic, rsaPrivate, new SHA3Digest(512), NISTObjectIdentifiers.id_sha3_512);
+
+        // Null format test
+        RSADigestSigner signer = new RSADigestSigner(new NullDigest());
+        
+        signer.init(true, rsaPrivate);
+
+        signer.update(new byte[16], 0, 16);
+
+        try
+        {
+            signer.generateSignature();
+            fail("no exception");
+        }
+        catch (CryptoException e)
+        {
+            isTrue(e.getMessage().startsWith("unable to encode signature: malformed DigestInfo"));
+        }
     }
 
     private void checkDigest(RSAKeyParameters rsaPublic, RSAPrivateCrtKeyParameters rsaPrivate, Digest digest, ASN1ObjectIdentifier digOid)
@@ -75,6 +99,43 @@
         }
     }
 
+    private void checkNullDigest(RSAKeyParameters rsaPublic, RSAPrivateCrtKeyParameters rsaPrivate, Digest digest, ASN1ObjectIdentifier digOid)
+        throws Exception
+    {
+        byte[] msg = new byte[] { 1, 6, 3, 32, 7, 43, 2, 5, 7, 78, 4, 23 };
+
+        RSADigestSigner signer = new RSADigestSigner(new NullDigest());
+
+        byte[] hash = new byte[digest.getDigestSize()];
+        digest.update(msg, 0, msg.length);
+        digest.doFinal(hash, 0);
+
+        DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digOid, DERNull.INSTANCE), hash);
+        byte[] infoEnc = digInfo.getEncoded();
+
+        signer.init(true, rsaPrivate);
+
+        signer.update(infoEnc, 0, infoEnc.length);
+
+        byte[] sig = signer.generateSignature();
+
+        signer = new RSADigestSigner(digest, digOid);
+        signer.init(false, rsaPublic);
+        signer.update(msg, 0, msg.length);
+        if (!signer.verifySignature(sig))
+        {
+            fail("NONE - RSA Digest Signer failed.");
+        }
+
+        signer = new RSADigestSigner(new NullDigest());
+        signer.init(false, rsaPublic);
+        signer.update(infoEnc, 0, infoEnc.length);
+        if (!signer.verifySignature(sig))
+        {
+            fail("NONE - RSA Digest Signer failed.");
+        }
+    }
+
     public static void main(String[] args)
     {
         runTest(new RSADigestSignerTest());
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSATest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSATest.java
index bc62770..39c1792 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/RSATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/RSATest.java
@@ -196,6 +196,54 @@
         System.getProperties().remove(PKCS1Encoding.NOT_STRICT_LENGTH_ENABLED_PROPERTY);
     }
 
+    private void testUnsafeModulusAndWrongExp()
+    {
+        try
+        {
+            try
+            {
+                new RSAKeyParameters(false, mod, pubExp.multiply(BigInteger.valueOf(2)));
+
+                fail("no exception");
+            }
+            catch (IllegalArgumentException e)
+            {
+                isTrue("RSA publicExponent is even".equals(e.getMessage()));
+            }
+
+            try
+            {
+                new RSAKeyParameters(false, mod.multiply(BigInteger.valueOf(2)), pubExp);
+
+                fail("no exception");
+            }
+            catch (IllegalArgumentException e)
+            {
+                isTrue("RSA modulus is even".equals(e.getMessage()));
+            }
+
+            try
+            {
+                new RSAKeyParameters(false, mod.multiply(BigInteger.valueOf(3)), pubExp);
+
+                fail("no exception");
+            }
+            catch (IllegalArgumentException e)
+            {
+                isTrue("RSA modulus has a small prime factor".equals(e.getMessage()));
+            }
+
+            System.getProperties().put("org.bouncycastle.rsa.allow_unsafe_mod", "true");
+
+            // this should now work (sigh...)
+            new RSAKeyParameters(false, mod.multiply(BigInteger.valueOf(3)), pubExp);
+        }
+        finally
+        {
+            System.getProperties().remove("org.bouncycastle.rsa.allow_unsafe_mod");
+        }
+    }
+
     private void testTruncatedPKCS1Block(RSAKeyParameters pubParameters, RSAKeyParameters privParameters)
     {
         checkForPKCS1Exception(pubParameters, privParameters, truncatedDataBlock, "block incorrect");
@@ -241,7 +289,7 @@
         {
             data = eng.processBlock(data, 0, data.length);
 
-            fail("missing data block not recognised");
+            fail("incorrect block not recognised");
         }
         catch (InvalidCipherTextException e)
         {
@@ -649,6 +697,7 @@
         testTruncatedPKCS1Block(pubParameters, privParameters);
         testWrongPaddingPKCS1Block(pubParameters, privParameters);
         test_CVE_2017_15361();
+        testUnsafeModulusAndWrongExp();
 
         try
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/RegressionTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/RegressionTest.java
index 98a859e..89c934f 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/RegressionTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/RegressionTest.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.crypto.test;
 
+import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
 
 public class RegressionTest
 {
@@ -89,6 +89,7 @@
             new PSSTest(),
             new CTSTest(),
             new NISTCTSTest(),
+            new NISTECCTest(),
             new CCMTest(),
             new PKCS5Test(),
             new PKCS12Test(),
@@ -107,6 +108,7 @@
             new Salsa20Test(),
             new XSalsa20Test(),
             new ChaChaTest(),
+            new ChaCha20Poly1305Test(),
             new CMacTest(),
             new EAXTest(),
             new GCMTest(),
@@ -147,6 +149,7 @@
             new X931SignerTest(),
             new Blake2bDigestTest(),
             new Blake2sDigestTest(),
+            new Blake2xsDigestTest(),
             new KeccakDigestTest(),
             new SHAKEDigestTest(),
             new SM2EngineTest(),
@@ -165,22 +168,18 @@
             new Ed448Test(),
             new CSHAKETest(),
             new Argon2Test(),
-            new OpenSSHKeyParsingTests()
+            new OpenSSHKeyParsingTests(),
+            new EthereumIESTest(),
+            new BigIntegersTest(),
+            new ZucTest(),
+            new Haraka256DigestTest(),
+            new Haraka512DigestTest(),
+            new KMACTest(),
+            new SipHash128Test()
         };
 
-    public static void main(
-        String[] args)
+    public static void main(String[] args)
     {
-        for (int i = 0; i != tests.length; i++)
-        {
-            TestResult result = tests[i].perform();
-
-            if (result.getException() != null)
-            {
-                result.getException().printStackTrace();
-            }
-
-            System.out.println(result);
-        }
+        SimpleTest.runTests(tests);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SCryptTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SCryptTest.java
index 9e547e5..334d676 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SCryptTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SCryptTest.java
@@ -2,15 +2,17 @@
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 
 import org.bouncycastle.crypto.generators.SCrypt;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
 /*
  * scrypt test vectors from "Stronger Key Derivation Via Sequential Memory-hard Functions" Appendix B.
- * (http://www.tarsnap.com/scrypt/scrypt.pdf)
+ * (https://www.tarsnap.com/scrypt/scrypt.pdf)
  */
 public class SCryptTest
     extends SimpleTest
@@ -23,6 +25,7 @@
     public void performTest()
         throws Exception
     {
+        testPermutations();
         testParameters();
         testVectors();
     }
@@ -42,6 +45,96 @@
         checkIllegal("Len parameter must be > 1", new byte[0], new byte[0], 2, 1, 1, 0);
     }
 
+
+    public void testPermutations()
+        throws Exception
+    {
+
+        byte[] rootPassword = Strings.toByteArray("aabcdd");
+        byte[] buf = null;
+
+        byte[][] salts = new byte[3][];
+
+        salts[0] = new byte[16];
+        salts[1] = new byte[16];
+        salts[2] = new byte[16];
+        for (int t = 0; t < 16; t++)
+        {
+            salts[1][t] = (byte)t;
+            salts[2][t] = (byte)(16 - t);
+        }
+
+        //
+        // Permutation, starting with a shorter array, same length then one longer.
+        //
+        for (int j = rootPassword.length - 1; j < rootPassword.length + 2; j++)
+        {
+            buf = new byte[j];
+
+            for (int a = 0; a < rootPassword.length; a++)
+            {
+                for (int b = 0; b < buf.length; b++)
+                {
+                    buf[b] = rootPassword[(a + b) % rootPassword.length];
+                }
+
+
+                ArrayList<byte[]> permutations = new ArrayList<byte[]>();
+                permute(permutations, buf, 0, buf.length - 1);
+
+                for (int i = 0; i != permutations.size(); i++)
+                {
+                    byte[] candidate = (byte[])permutations.get(i);
+                    for (int k = 0; k != salts.length; k++)
+                    {
+                        byte[] salt = salts[k];
+                        byte[] expected = SCrypt.generate(rootPassword,salt,   2,1,1,32);
+                        byte[] testValue = SCrypt.generate(candidate,salt,   2,1,1,32);
+
+                        //
+                        // If the passwords are the same for the same salt we should have the same string.
+                        //
+                        boolean sameAsRoot = Arrays.areEqual(rootPassword, candidate);
+                        isTrue("expected same result", sameAsRoot == Arrays.areEqual(expected, testValue));
+
+                    }
+
+                }
+            }
+        }
+    }
+
+    private void swap(byte[] buf, int i, int j)
+    {
+        byte b = buf[i];
+        buf[i] = buf[j];
+        buf[j] = b;
+    }
+
+    private void permute(ArrayList<byte[]> permutation, byte[] a, int l, int r)
+    {
+        if (l == r)
+        {
+            permutation.add(Arrays.clone(a));
+        }
+        else
+        {
+
+            for (int i = l; i <= r; i++)
+            {
+                // Swapping done
+                swap(a, l, i);
+
+                // Recursion called
+                permute(permutation, a, l + 1, r);
+
+                //backtrack
+                swap(a, l, i);
+            }
+        }
+    }
+
+
     private void checkOK(String msg, byte[] pass, byte[] salt, int N, int r, int p, int len)
     {
         try
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SEEDTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SEEDTest.java
index 4aa955b..e1b1831 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SEEDTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SEEDTest.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * SEED tester - vectors http://www.ietf.org/rfc/rfc4009.txt
+ * SEED tester - vectors https://www.ietf.org/rfc/rfc4009.txt
  */
 public class SEEDTest
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2EngineTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2EngineTest.java
index a9cbad0..09a641c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2EngineTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2EngineTest.java
@@ -107,6 +107,85 @@
         isTrue("dec wrong", Arrays.areEqual(m, dec));
     }
 
+    private void doEngineTestFpC1C3C2()
+        throws Exception
+    {
+        BigInteger SM2_ECC_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16);
+        BigInteger SM2_ECC_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16);
+        BigInteger SM2_ECC_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16);
+        BigInteger SM2_ECC_N = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DD297720630485628D5AE74EE7C32E79B7", 16);
+        BigInteger SM2_ECC_H = ECConstants.ONE;
+        BigInteger SM2_ECC_GX = new BigInteger("421DEBD61B62EAB6746434EBC3CC315E32220B3BADD50BDC4C4E6C147FEDD43D", 16);
+        BigInteger SM2_ECC_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16);
+
+        ECCurve curve = new ECCurve.Fp(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
+
+        ECPoint g = curve.createPoint(SM2_ECC_GX, SM2_ECC_GY);
+        ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);
+
+        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+
+        ECKeyGenerationParameters aKeyGenParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0", 16));
+
+        keyPairGenerator.init(aKeyGenParams);
+
+        AsymmetricCipherKeyPair aKp = keyPairGenerator.generateKeyPair();
+
+        ECPublicKeyParameters aPub = (ECPublicKeyParameters)aKp.getPublic();
+        ECPrivateKeyParameters aPriv = (ECPrivateKeyParameters)aKp.getPrivate();
+
+        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
+
+        byte[] m = Strings.toByteArray("encryption standard");
+
+        sm2Engine.init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16)));
+
+        byte[] enc = sm2Engine.processBlock(m, 0, m.length);
+
+        isTrue("enc wrong", Arrays.areEqual(Hex.decode(
+            "04245C26 FB68B1DD DDB12C4B 6BF9F2B6 D5FE60A3 83B0D18D 1C4144AB F17F6252" +
+            "E776CB92 64C2A7E8 8E52B199 03FDC473 78F605E3 6811F5C0 7423A24B 84400F01" +
+            "B8 9C3D7360 C30156FA B7C80A02" +
+            "76712DA9 D8094A63 4B766D3A 285E0748 0653426D 650053 A89B41C4 18B0C3AA D00D886C 00286467"), enc));
+
+        sm2Engine.init(false, aPriv);
+
+        byte[] dec = sm2Engine.processBlock(enc, 0, enc.length);
+
+        isTrue("dec wrong", Arrays.areEqual(m, dec));
+
+        enc[80] = (byte)(enc[80] + 1);
+
+        try
+        {
+            sm2Engine.processBlock(enc, 0, enc.length);
+            fail("no exception");
+        }
+        catch (InvalidCipherTextException e)
+        {
+            isTrue("wrong exception", "invalid cipher text".equals(e.getMessage()));
+        }
+
+        // long message
+        sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
+
+        m = new byte[4097];
+        for (int i = 0; i != m.length; i++)
+        {
+            m[i] = (byte)i;
+        }
+
+        sm2Engine.init(true, new ParametersWithRandom(aPub, new TestRandomBigInteger("4C62EEFD6ECFC2B95B92FD6C3D9575148AFA17425546D49018E5388D49DD7B4F", 16)));
+
+        enc = sm2Engine.processBlock(m, 0, m.length);
+
+        sm2Engine.init(false, aPriv);
+
+        dec = sm2Engine.processBlock(enc, 0, enc.length);
+
+        isTrue("dec wrong", Arrays.areEqual(m, dec));
+    }
+
     private void doEngineTestF2m()
         throws Exception
     {
@@ -159,6 +238,7 @@
     {
         doEngineTestFp();
         doEngineTestF2m();
+        doEngineTestFpC1C3C2();
     }
 
     public static void main(String[] args)
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2SignerTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2SignerTest.java
index 51c9bcb..e964a7c 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2SignerTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM2SignerTest.java
@@ -7,12 +7,15 @@
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SM3Digest;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
-import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
-import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.ParametersWithID;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.crypto.signers.SM2Signer;
@@ -26,14 +29,196 @@
 public class SM2SignerTest
     extends SimpleTest
 {
+    private static final ECDomainParameters PARAMS_FP_DRAFT = createParamsFpDraft();
+    private static final ECDomainParameters PARAMS_F2M = createParamsF2m();
+
     public String getName()
     {
         return "SM2Signer";
     }
 
-    private void doSignerTestFp()
+    private void doSignerTestFpDraftSM3()
         throws Exception
     {
+        doSignerTest(
+            PARAMS_FP_DRAFT,
+            new SM3Digest(),
+            "ALICE123@YAHOO.COM",
+            "message digest",
+            "128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263",
+            "6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F",
+            "40F1EC59F793D9F49E09DCEF49130D4194F79FB1EED2CAA55BACDB49C4E755D1",
+            "6FC6DAC32C5D5CF10C77DFB20F7C2EB667A457872FB09EC56327A67EC7DEEBE7"
+        );
+    }
+
+    private void doSignerTestFpDraftSha256()
+        throws Exception
+    {
+        doSignerTest(
+            PARAMS_FP_DRAFT,
+            new SHA256Digest(),
+            "ALICE123@YAHOO.COM",
+            "message digest",
+            "128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263",
+            "6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F",
+            "7D62A5EDBDDC8AF4D69C9E37A60D31F5CEFE8727709117E0869648D0A9AE4F57",
+            "1E5E89718B716AAFC6253443168E4F7CF7E1B7B3934307686CE5947C1BD55EDA"
+        );
+    }
+
+    private void doSignerTestFpStandardSM3()
+        throws Exception
+    {
+        doSignerTest(
+            "sm2p256v1",
+            new SM3Digest(),
+            "sm2test@example.com",
+            "hi chappy",
+            "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC",
+            "3174C6FFC3C279D2422F3FC0A9F3E574674A4490FE45A5325CAF7D3EC4C8F96C",
+            "05890B9077B92E47B17A1FF42A814280E556AFD92B4A98B9670BF8B1A274C2FA",
+            "E3ABBB8DB2B6ECD9B24ECCEA7F679FB9A4B1DB52F4AA985E443AD73237FA1993"
+        );
+    }
+
+    private void doSignerTestFpStandardSha256()
+        throws Exception
+    {
+        doSignerTest(
+            "sm2p256v1",
+            new SHA256Digest(),
+            "sm2test@example.com",
+            "hi chappy",
+            "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC",
+            "3174C6FFC3C279D2422F3FC0A9F3E574674A4490FE45A5325CAF7D3EC4C8F96C",
+            "94DA20EA69E4FC70692158BF3D30F87682A4B2F84DF4A4829A1EFC5D9C979D3F",
+            "EE15AF8D455B728AB80E592FCB654BF5B05620B2F4D25749D263D5C01FAD365F"
+        );
+    }
+
+    private void doSignerTestFpP256SM3()
+        throws Exception
+    {
+        doSignerTest(
+            "P-256",
+            new SM3Digest(),
+            "sm2_p256_test@example.com",
+            "no backdoors here",
+            "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC",
+            "3174C6FFC3C279D2422F3FC0A9F3E574674A4490FE45A5325CAF7D3EC4C8F96C",
+            "96AA39A0C4A5C454653F394E86386F2E38BE14C57D0E555F3A27A5CEF30E51BD",
+            "62372BE4AC97DBE725AC0B279BB8FD15883858D814FD792DDB0A401DCC988E70"
+        );
+    }
+
+    private void doSignerTestFpP256Sha256()
+        throws Exception
+    {
+        doSignerTest(
+            "P-256",
+            new SHA256Digest(),
+            "sm2_p256_test@example.com",
+            "no backdoors here",
+            "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC",
+            "3174C6FFC3C279D2422F3FC0A9F3E574674A4490FE45A5325CAF7D3EC4C8F96C",
+            "503D234A22123D7029271EB9E0D763619A69868DE8296C13EDD4CA32D280CFDE",
+            "0BDE97699B77268584DDD238DA120095F01130AD2DB37184270F37C02FB2E86B"
+        );
+    }
+
+    private void doSignerTestF2m()
+        throws Exception
+    {
+        doSignerTest(
+            PARAMS_F2M,
+            new SM3Digest(),
+            "ALICE123@YAHOO.COM",
+            "message digest",
+            "771EF3DBFF5F1CDC32B9C572930476191998B2BF7CB981D7F5B39202645F0931",
+            "36CD79FC8E24B7357A8A7B4A46D454C397703D6498158C605399B341ADA186D6",
+            "6D3FBA26EAB2A1054F5D198332E335817C8AC453ED26D3391CD4439D825BF25B",
+            "3124C5688D95F0A10252A9BED033BEC84439DA384621B6D6FAD77F94B74A9556"
+        );
+    }
+
+    private void doSignerTest(String curveName, Digest d, String ident, String msg, String x, String nonce, String r, String s)
+        throws Exception
+    {
+        X9ECParameters x9 = ECNamedCurveTable.getByName(curveName);
+        ECDomainParameters domainParams = new ECDomainParameters(x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+
+        doSignerTest(domainParams, d, ident, msg, x, nonce, r, s);
+    }
+
+    private void doSignerTest(ECDomainParameters domainParams, Digest d, String ident, String msg, String x, String nonce, String r, String s)
+        throws Exception
+    {
+        byte[] idBytes = Strings.toByteArray(ident);
+        byte[] msgBytes = Strings.toByteArray(msg);
+        AsymmetricCipherKeyPair kp = generateKeyPair(domainParams, x);
+
+        SM2Signer signer = new SM2Signer(d);
+
+        signer.init(true, new ParametersWithID(
+            new ParametersWithRandom(kp.getPrivate(), new TestRandomBigInteger(nonce, 16)),
+            idBytes));
+
+        signer.update(msgBytes, 0, msgBytes.length);
+
+        byte[] sig = signer.generateSignature();
+
+        BigInteger[] rs = decode(sig);
+
+        isTrue("r wrong", rs[0].equals(new BigInteger(r, 16)));
+        isTrue("s wrong", rs[1].equals(new BigInteger(s, 16)));
+
+        signer.init(false, new ParametersWithID(kp.getPublic(), idBytes));
+
+        signer.update(msgBytes, 0, msgBytes.length);
+
+        isTrue("verification failed", signer.verifySignature(sig));
+    }
+
+    private void doVerifyBoundsCheck()
+        throws IOException
+    {
+        ECDomainParameters domainParams = PARAMS_F2M;
+
+        AsymmetricCipherKeyPair kp = generateKeyPair(domainParams, "771EF3DBFF5F1CDC32B9C572930476191998B2BF7CB981D7F5B39202645F0931");
+
+        SM2Signer signer = new SM2Signer();
+
+        signer.init(false, kp.getPublic());
+
+        signer.update(new byte[20], 0, 20);
+        isTrue(!signer.verifySignature(encode(ECConstants.ZERO, ECConstants.EIGHT)));
+
+        signer.update(new byte[20], 0, 20);
+        isTrue(!signer.verifySignature(encode(ECConstants.EIGHT, ECConstants.ZERO)));
+
+        signer.update(new byte[20], 0, 20);
+        isTrue(!signer.verifySignature(encode(domainParams.getN(), ECConstants.EIGHT)));
+
+        signer.update(new byte[20], 0, 20);
+        isTrue(!signer.verifySignature(encode(ECConstants.EIGHT, domainParams.getN())));
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        doSignerTestFpDraftSM3();
+        doSignerTestFpDraftSha256();
+        doSignerTestFpStandardSM3();
+        doSignerTestFpStandardSha256();
+        doSignerTestFpP256SM3();
+        doSignerTestFpP256Sha256();
+        doSignerTestF2m();
+        doVerifyBoundsCheck();
+    }
+
+    private static ECDomainParameters createParamsFpDraft()
+    {
         BigInteger SM2_ECC_P = new BigInteger("8542D69E4C044F18E8B92435BF6FF7DE457283915C45517D722EDB8B08F1DFC3", 16);
         BigInteger SM2_ECC_A = new BigInteger("787968B4FA32C3FD2417842E73BBFEFF2F3C848B6831D7E0EC65228B3937E498", 16);
         BigInteger SM2_ECC_B = new BigInteger("63E4C6D3B23B0C849CF84241484BFE48F61D59A5B16BA06E6E12D1DA27C5249A", 16);
@@ -43,48 +228,11 @@
         BigInteger SM2_ECC_GY = new BigInteger("0680512BCBB42C07D47349D2153B70C4E5D7FDFCBFA36EA1A85841B9E46E09A2", 16);
 
         ECCurve curve = new ECCurve.Fp(SM2_ECC_P, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
-
         ECPoint g = curve.createPoint(SM2_ECC_GX, SM2_ECC_GY);
-        ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);
-
-        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("128B2FA8BD433C6C068C8D803DFF79792A519A55171B1B650C23661D15897263", 16));
-        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
-
-        keyPairGenerator.init(keyGenerationParams);
-        AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair();
-
-        ECPublicKeyParameters ecPub = (ECPublicKeyParameters)kp.getPublic();
-        ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters)kp.getPrivate();
-
-        SM2Signer signer = new SM2Signer();
-
-        signer.init(true,
-            new ParametersWithID(new ParametersWithRandom(ecPriv,
-                    new TestRandomBigInteger("6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F", 16)),
-                Strings.toByteArray("ALICE123@YAHOO.COM")));
-
-        byte[] msg = Strings.toByteArray("message digest");
-
-        signer.update(msg, 0, msg.length);
-
-        byte[] sig = signer.generateSignature();
-
-        BigInteger[] rs = decode(sig);
-
-        isTrue("r wrong", rs[0].equals(new BigInteger("40F1EC59F793D9F49E09DCEF49130D4194F79FB1EED2CAA55BACDB49C4E755D1", 16)));
-        isTrue("s wrong", rs[1].equals(new BigInteger("6FC6DAC32C5D5CF10C77DFB20F7C2EB667A457872FB09EC56327A67EC7DEEBE7", 16)));
-
-        signer = new SM2Signer();
-
-        signer.init(false, new ParametersWithID(ecPub, Strings.toByteArray("ALICE123@YAHOO.COM")));
-
-        signer.update(msg, 0, msg.length);
-
-        isTrue("verification failed", signer.verifySignature(sig));
+        return new ECDomainParameters(curve, g, SM2_ECC_N, SM2_ECC_H);
     }
 
-    private void doSignerTestF2m()
-        throws Exception
+    private static ECDomainParameters createParamsF2m()
     {
         BigInteger SM2_ECC_A = new BigInteger("00", 16);
         BigInteger SM2_ECC_B = new BigInteger("E78BCD09746C202378A7E72B12BCE00266B9627ECB0B5A25367AD1AD4CC6242B", 16);
@@ -94,98 +242,22 @@
         BigInteger SM2_ECC_GY = new BigInteger("013DE74DA65951C4D76DC89220D5F7777A611B1C38BAE260B175951DC8060C2B3E", 16);
 
         ECCurve curve = new ECCurve.F2m(257, 12, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
-
         ECPoint g = curve.createPoint(SM2_ECC_GX, SM2_ECC_GY);
-        ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);
-
-        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("771EF3DBFF5F1CDC32B9C572930476191998B2BF7CB981D7F5B39202645F0931", 16));
-        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
-
-        keyPairGenerator.init(keyGenerationParams);
-        AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair();
-
-        ECPublicKeyParameters ecPub = (ECPublicKeyParameters)kp.getPublic();
-        ECPrivateKeyParameters ecPriv = (ECPrivateKeyParameters)kp.getPrivate();
-
-        SM2Signer signer = new SM2Signer();
-
-        signer.init(true,
-            new ParametersWithID(new ParametersWithRandom(ecPriv,
-                    new TestRandomBigInteger("36CD79FC8E24B7357A8A7B4A46D454C397703D6498158C605399B341ADA186D6", 16)),
-                Strings.toByteArray("ALICE123@YAHOO.COM")));
-
-        byte[] msg = Strings.toByteArray("message digest");
-
-        signer.update(msg, 0, msg.length);
-
-        byte[] sig = signer.generateSignature();
-
-        BigInteger[] rs = decode(sig);
-
-        isTrue("F2m r wrong", rs[0].equals(new BigInteger("6D3FBA26EAB2A1054F5D198332E335817C8AC453ED26D3391CD4439D825BF25B", 16)));
-        isTrue("F2m s wrong", rs[1].equals(new BigInteger("3124C5688D95F0A10252A9BED033BEC84439DA384621B6D6FAD77F94B74A9556", 16)));
-
-        signer.init(false, new ParametersWithID(ecPub, Strings.toByteArray("ALICE123@YAHOO.COM")));
-
-        signer.update(msg, 0, msg.length);
-
-        isTrue("verification failed", signer.verifySignature(sig));
-    }
-
-    private void doVerifyBoundsCheck()
-        throws IOException
-    {
-        BigInteger SM2_ECC_A = new BigInteger("00", 16);
-        BigInteger SM2_ECC_B = new BigInteger("E78BCD09746C202378A7E72B12BCE00266B9627ECB0B5A25367AD1AD4CC6242B", 16);
-        BigInteger SM2_ECC_N = new BigInteger("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC972CF7E6B6F900945B3C6A0CF6161D", 16);
-        BigInteger SM2_ECC_H = BigInteger.valueOf(4);
-        BigInteger SM2_ECC_GX = new BigInteger("00CDB9CA7F1E6B0441F658343F4B10297C0EF9B6491082400A62E7A7485735FADD", 16);
-        BigInteger SM2_ECC_GY = new BigInteger("013DE74DA65951C4D76DC89220D5F7777A611B1C38BAE260B175951DC8060C2B3E", 16);
-
-        ECCurve curve = new ECCurve.F2m(257, 12, SM2_ECC_A, SM2_ECC_B, SM2_ECC_N, SM2_ECC_H);
-
-        ECPoint g = curve.createPoint(SM2_ECC_GX, SM2_ECC_GY);
-        ECDomainParameters domainParams = new ECDomainParameters(curve, g, SM2_ECC_N);
-
-        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger("771EF3DBFF5F1CDC32B9C572930476191998B2BF7CB981D7F5B39202645F0931", 16));
-        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
-
-        keyPairGenerator.init(keyGenerationParams);
-        AsymmetricCipherKeyPair kp = keyPairGenerator.generateKeyPair();
-
-        ECPublicKeyParameters ecPub = (ECPublicKeyParameters)kp.getPublic();
-
-        SM2Signer signer = new SM2Signer();
-
-        signer.init(false, ecPub);
-
-        signer.update(new byte[20], 0, 20);
-        isTrue(!signer.verifySignature(encode(ECConstants.ZERO, ECConstants.EIGHT)));
-
-        signer.update(new byte[20], 0, 20);
-        isTrue(!signer.verifySignature(encode(ECConstants.EIGHT, ECConstants.ZERO)));
-
-        signer.update(new byte[20], 0, 20);
-        isTrue(!signer.verifySignature(encode(SM2_ECC_N, ECConstants.EIGHT)));
-
-        signer.update(new byte[20], 0, 20);
-        isTrue(!signer.verifySignature(encode(ECConstants.EIGHT, SM2_ECC_N)));
-    }
-
-    public void performTest()
-        throws Exception
-    {
-        doSignerTestFp();
-        doSignerTestF2m();
-        doVerifyBoundsCheck();
+        return new ECDomainParameters(curve, g, SM2_ECC_N, SM2_ECC_H);
     }
 
     private static BigInteger[] decode(byte[] sig)
     {
         ASN1Sequence s = ASN1Sequence.getInstance(sig);
 
-        return new BigInteger[] { ASN1Integer.getInstance(s.getObjectAt(0)).getValue(),
-            ASN1Integer.getInstance(s.getObjectAt(1)).getValue() };
+        return new BigInteger[] {
+            decodeValue(s.getObjectAt(0)),
+            decodeValue(s.getObjectAt(1)) };
+    }
+
+    private static BigInteger decodeValue(ASN1Encodable e)
+    {
+        return ASN1Integer.getInstance(e).getValue();
     }
 
     private static byte[] encode(BigInteger r, BigInteger s)
@@ -194,6 +266,13 @@
         return new DERSequence(new ASN1Encodable[] { new ASN1Integer(r), new ASN1Integer(s)}).getEncoded();
     }
 
+    private static AsymmetricCipherKeyPair generateKeyPair(ECDomainParameters domainParams, String x)
+    {
+        ECKeyPairGenerator kpg = new ECKeyPairGenerator();
+        kpg.init(new ECKeyGenerationParameters(domainParams, new TestRandomBigInteger(x, 16)));
+        return kpg.generateKeyPair();
+    }
+
     public static void main(String[] args)
     {
         runTest(new SM2SignerTest());
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java
index 7c12e5b..a923573 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SM4Test.java
@@ -1,7 +1,12 @@
 package org.bouncycastle.crypto.test;
 
 import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
 import org.bouncycastle.crypto.engines.SM4Engine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.CCMBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
@@ -30,6 +35,8 @@
         super.performTest();
 
         test1000000();
+        gcmTest();
+        ccmTest();
     }
 
     private void test1000000()
@@ -68,6 +75,122 @@
         }
     }
 
+    private void gcmTest()
+        throws Exception
+    {
+        byte[] iv = Hex.decode("00001234567800000000ABCD");
+        byte[] key = Hex.decode("0123456789ABCDEFFEDCBA9876543210");
+        byte[] pt = Hex.decode(  "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB"
+                               + "CCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD"
+                               + "EEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF"
+                               + "EEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA");
+        byte[] aad = Hex.decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2");
+        byte[] ct = Hex.decode("17F399F08C67D5EE19D0DC9969C4BB7D"
+                             + "5FD46FD3756489069157B282BB200735"
+                             + "D82710CA5C22F0CCFA7CBF93D496AC15"
+                             + "A56834CBCF98C397B4024A2691233B8D");
+        byte[] tag = Hex.decode("83DE3541E4C2B58177E065A9BF7B62EC");
+
+        GCMBlockCipher encCipher = new GCMBlockCipher(new SM4Engine());
+        GCMBlockCipher  decCipher = new GCMBlockCipher(new SM4Engine());
+
+        checkTestCase(encCipher, decCipher, "1", key, iv, aad, pt, ct, tag);
+    }
+
+    private void ccmTest()
+        throws Exception
+    {
+        byte[] iv = Hex.decode("00001234567800000000ABCD");
+        byte[] key = Hex.decode("0123456789ABCDEFFEDCBA9876543210");
+        byte[] pt = Hex.decode(  "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB"
+                               + "CCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD"
+                               + "EEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF"
+                               + "EEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA");
+        byte[] aad = Hex.decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2");
+        byte[] ct = Hex.decode("48AF93501FA62ADBCD414CCE6034D895"
+                             + "DDA1BF8F132F042098661572E7483094"
+                             + "FD12E518CE062C98ACEE28D95DF4416B"
+                             + "ED31A2F04476C18BB40C84A74B97DC5B");
+        byte[] tag = Hex.decode("fe26a58f94552a8d533b5b6b261c9cd8");
+
+        CCMBlockCipher encCipher = new CCMBlockCipher(new SM4Engine());
+        CCMBlockCipher  decCipher = new CCMBlockCipher(new SM4Engine());
+
+        checkTestCase(encCipher, decCipher, "2", key, iv, aad, pt, ct, tag);
+    }
+
+    private void checkTestCase(
+        AEADBlockCipher encCipher,
+        AEADBlockCipher decCipher,
+        String          testName,
+        byte[]          K,
+        byte[]          IV,
+        byte[]          SA,
+        byte[]          P,
+        byte[]          C,
+        byte[]          T)
+        throws InvalidCipherTextException
+    {
+        encCipher.init(true, new AEADParameters(new KeyParameter(K), T.length * 8, IV));
+        decCipher.init(false, new AEADParameters(new KeyParameter(K), T.length * 8, IV));
+        
+        byte[] enc = new byte[encCipher.getOutputSize(P.length)];
+        if (SA != null)
+        {
+            encCipher.processAADBytes(SA, 0, SA.length);
+        }
+        int len = encCipher.processBytes(P, 0, P.length, enc, 0);
+        len += encCipher.doFinal(enc, len);
+
+        if (enc.length != len)
+        {
+//            System.out.println("" + enc.length + "/" + len);
+            fail("encryption reported incorrect length: " + testName);
+        }
+
+        byte[] mac = encCipher.getMac();
+//         System.err.println(Hex.toHexString(enc));
+        byte[] data = new byte[P.length];
+        System.arraycopy(enc, 0, data, 0, data.length);
+        byte[] tail = new byte[enc.length - P.length];
+        System.arraycopy(enc, P.length, tail, 0, tail.length);
+
+        if (!areEqual(C, data))
+        {
+            fail("incorrect encrypt in: " + testName);
+        }
+  
+        if (!areEqual(T, mac))
+        {
+            fail("getMac() returned wrong mac in: " + testName);
+        }
+
+        if (encCipher instanceof GCMBlockCipher)
+        {
+            if (!areEqual(T, tail))
+            {
+                fail("stream contained wrong mac in: " + testName);
+            }
+        }
+
+        byte[] dec = new byte[decCipher.getOutputSize(enc.length)];
+        if (SA != null)
+        {
+            decCipher.processAADBytes(SA, 0, SA.length);
+        }
+        len = decCipher.processBytes(enc, 0, enc.length, dec, 0);
+        len += decCipher.doFinal(dec, len);
+        mac = decCipher.getMac();
+       
+        data = new byte[C.length];
+        System.arraycopy(dec, 0, data, 0, data.length);
+
+        if (!areEqual(P, data))
+        {
+            fail("incorrect decrypt in: " + testName);
+        }
+    }
+
     public String getName()
     {
         return "SM4";
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SimpleTestTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SimpleTestTest.java
new file mode 100644
index 0000000..11e491d
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SimpleTestTest.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.crypto.test;
+
+import junit.framework.TestCase;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class SimpleTestTest
+    extends TestCase
+{
+    public void testCrypto()
+    {
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                if (result.getException() != null)
+                {
+                    result.getException().printStackTrace();
+                }
+                fail(result.toString());
+            }
+        }
+    }
+}
+
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHash128Test.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHash128Test.java
new file mode 100644
index 0000000..1c0d21f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHash128Test.java
@@ -0,0 +1,314 @@
+package org.bouncycastle.crypto.test;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.macs.SipHash128;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/*
+ * SipHash test values are taken from the output of the C reference implementations:
+ * section "const uint8_t vectors_sip128[64][16]" of the output of command ./vectors.
+ */
+public class SipHash128Test
+    extends SimpleTest
+{
+    private static final int UPDATE_BYTES = 0;
+    private static final int UPDATE_FULL = 1;
+    private static final int UPDATE_MIX = 2;
+
+    public String getName()
+    {
+        return "SipHash128";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        performTest_2_4();
+        performTest_4_8();
+    }
+
+    private void performTest_2_4()
+        throws Exception
+    {
+        /*
+         * SipHash test values are taken from the output of the C reference implementations:
+         * section "const uint8_t vectors_sip128[64][16]" of the output of command ./vectors.
+         */
+        int[][] vectors_sip128 = new int[][] {
+            { 0xa3, 0x81, 0x7f, 0x04, 0xba, 0x25, 0xa8, 0xe6, 0x6d, 0xf6, 0x72, 0x14, 0xc7, 0x55, 0x02, 0x93, },
+            { 0xda, 0x87, 0xc1, 0xd8, 0x6b, 0x99, 0xaf, 0x44, 0x34, 0x76, 0x59, 0x11, 0x9b, 0x22, 0xfc, 0x45, },
+            { 0x81, 0x77, 0x22, 0x8d, 0xa4, 0xa4, 0x5d, 0xc7, 0xfc, 0xa3, 0x8b, 0xde, 0xf6, 0x0a, 0xff, 0xe4, },
+            { 0x9c, 0x70, 0xb6, 0x0c, 0x52, 0x67, 0xa9, 0x4e, 0x5f, 0x33, 0xb6, 0xb0, 0x29, 0x85, 0xed, 0x51, },
+            { 0xf8, 0x81, 0x64, 0xc1, 0x2d, 0x9c, 0x8f, 0xaf, 0x7d, 0x0f, 0x6e, 0x7c, 0x7b, 0xcd, 0x55, 0x79, },
+            { 0x13, 0x68, 0x87, 0x59, 0x80, 0x77, 0x6f, 0x88, 0x54, 0x52, 0x7a, 0x07, 0x69, 0x0e, 0x96, 0x27, },
+            { 0x14, 0xee, 0xca, 0x33, 0x8b, 0x20, 0x86, 0x13, 0x48, 0x5e, 0xa0, 0x30, 0x8f, 0xd7, 0xa1, 0x5e, },
+            { 0xa1, 0xf1, 0xeb, 0xbe, 0xd8, 0xdb, 0xc1, 0x53, 0xc0, 0xb8, 0x4a, 0xa6, 0x1f, 0xf0, 0x82, 0x39, },
+            { 0x3b, 0x62, 0xa9, 0xba, 0x62, 0x58, 0xf5, 0x61, 0x0f, 0x83, 0xe2, 0x64, 0xf3, 0x14, 0x97, 0xb4, },
+            { 0x26, 0x44, 0x99, 0x06, 0x0a, 0xd9, 0xba, 0xab, 0xc4, 0x7f, 0x8b, 0x02, 0xbb, 0x6d, 0x71, 0xed, },
+            { 0x00, 0x11, 0x0d, 0xc3, 0x78, 0x14, 0x69, 0x56, 0xc9, 0x54, 0x47, 0xd3, 0xf3, 0xd0, 0xfb, 0xba, },
+            { 0x01, 0x51, 0xc5, 0x68, 0x38, 0x6b, 0x66, 0x77, 0xa2, 0xb4, 0xdc, 0x6f, 0x81, 0xe5, 0xdc, 0x18, },
+            { 0xd6, 0x26, 0xb2, 0x66, 0x90, 0x5e, 0xf3, 0x58, 0x82, 0x63, 0x4d, 0xf6, 0x85, 0x32, 0xc1, 0x25, },
+            { 0x98, 0x69, 0xe2, 0x47, 0xe9, 0xc0, 0x8b, 0x10, 0xd0, 0x29, 0x93, 0x4f, 0xc4, 0xb9, 0x52, 0xf7, },
+            { 0x31, 0xfc, 0xef, 0xac, 0x66, 0xd7, 0xde, 0x9c, 0x7e, 0xc7, 0x48, 0x5f, 0xe4, 0x49, 0x49, 0x02, },
+            { 0x54, 0x93, 0xe9, 0x99, 0x33, 0xb0, 0xa8, 0x11, 0x7e, 0x08, 0xec, 0x0f, 0x97, 0xcf, 0xc3, 0xd9, },
+            { 0x6e, 0xe2, 0xa4, 0xca, 0x67, 0xb0, 0x54, 0xbb, 0xfd, 0x33, 0x15, 0xbf, 0x85, 0x23, 0x05, 0x77, },
+            { 0x47, 0x3d, 0x06, 0xe8, 0x73, 0x8d, 0xb8, 0x98, 0x54, 0xc0, 0x66, 0xc4, 0x7a, 0xe4, 0x77, 0x40, },
+            { 0xa4, 0x26, 0xe5, 0xe4, 0x23, 0xbf, 0x48, 0x85, 0x29, 0x4d, 0xa4, 0x81, 0xfe, 0xae, 0xf7, 0x23, },
+            { 0x78, 0x01, 0x77, 0x31, 0xcf, 0x65, 0xfa, 0xb0, 0x74, 0xd5, 0x20, 0x89, 0x52, 0x51, 0x2e, 0xb1, },
+            { 0x9e, 0x25, 0xfc, 0x83, 0x3f, 0x22, 0x90, 0x73, 0x3e, 0x93, 0x44, 0xa5, 0xe8, 0x38, 0x39, 0xeb, },
+            { 0x56, 0x8e, 0x49, 0x5a, 0xbe, 0x52, 0x5a, 0x21, 0x8a, 0x22, 0x14, 0xcd, 0x3e, 0x07, 0x1d, 0x12, },
+            { 0x4a, 0x29, 0xb5, 0x45, 0x52, 0xd1, 0x6b, 0x9a, 0x46, 0x9c, 0x10, 0x52, 0x8e, 0xff, 0x0a, 0xae, },
+            { 0xc9, 0xd1, 0x84, 0xdd, 0xd5, 0xa9, 0xf5, 0xe0, 0xcf, 0x8c, 0xe2, 0x9a, 0x9a, 0xbf, 0x69, 0x1c, },
+            { 0x2d, 0xb4, 0x79, 0xae, 0x78, 0xbd, 0x50, 0xd8, 0x88, 0x2a, 0x8a, 0x17, 0x8a, 0x61, 0x32, 0xad, },
+            { 0x8e, 0xce, 0x5f, 0x04, 0x2d, 0x5e, 0x44, 0x7b, 0x50, 0x51, 0xb9, 0xea, 0xcb, 0x8d, 0x8f, 0x6f, },
+            { 0x9c, 0x0b, 0x53, 0xb4, 0xb3, 0xc3, 0x07, 0xe8, 0x7e, 0xae, 0xe0, 0x86, 0x78, 0x14, 0x1f, 0x66, },
+            { 0xab, 0xf2, 0x48, 0xaf, 0x69, 0xa6, 0xea, 0xe4, 0xbf, 0xd3, 0xeb, 0x2f, 0x12, 0x9e, 0xeb, 0x94, },
+            { 0x06, 0x64, 0xda, 0x16, 0x68, 0x57, 0x4b, 0x88, 0xb9, 0x35, 0xf3, 0x02, 0x73, 0x58, 0xae, 0xf4, },
+            { 0xaa, 0x4b, 0x9d, 0xc4, 0xbf, 0x33, 0x7d, 0xe9, 0x0c, 0xd4, 0xfd, 0x3c, 0x46, 0x7c, 0x6a, 0xb7, },
+            { 0xea, 0x5c, 0x7f, 0x47, 0x1f, 0xaf, 0x6b, 0xde, 0x2b, 0x1a, 0xd7, 0xd4, 0x68, 0x6d, 0x22, 0x87, },
+            { 0x29, 0x39, 0xb0, 0x18, 0x32, 0x23, 0xfa, 0xfc, 0x17, 0x23, 0xde, 0x4f, 0x52, 0xc4, 0x3d, 0x35, },
+            { 0x7c, 0x39, 0x56, 0xca, 0x5e, 0xea, 0xfc, 0x3e, 0x36, 0x3e, 0x9d, 0x55, 0x65, 0x46, 0xeb, 0x68, },
+            { 0x77, 0xc6, 0x07, 0x71, 0x46, 0xf0, 0x1c, 0x32, 0xb6, 0xb6, 0x9d, 0x5f, 0x4e, 0xa9, 0xff, 0xcf, },
+            { 0x37, 0xa6, 0x98, 0x6c, 0xb8, 0x84, 0x7e, 0xdf, 0x09, 0x25, 0xf0, 0xf1, 0x30, 0x9b, 0x54, 0xde, },
+            { 0xa7, 0x05, 0xf0, 0xe6, 0x9d, 0xa9, 0xa8, 0xf9, 0x07, 0x24, 0x1a, 0x2e, 0x92, 0x3c, 0x8c, 0xc8, },
+            { 0x3d, 0xc4, 0x7d, 0x1f, 0x29, 0xc4, 0x48, 0x46, 0x1e, 0x9e, 0x76, 0xed, 0x90, 0x4f, 0x67, 0x11, },
+            { 0x0d, 0x62, 0xbf, 0x01, 0xe6, 0xfc, 0x0e, 0x1a, 0x0d, 0x3c, 0x47, 0x51, 0xc5, 0xd3, 0x69, 0x2b, },
+            { 0x8c, 0x03, 0x46, 0x8b, 0xca, 0x7c, 0x66, 0x9e, 0xe4, 0xfd, 0x5e, 0x08, 0x4b, 0xbe, 0xe7, 0xb5, },
+            { 0x52, 0x8a, 0x5b, 0xb9, 0x3b, 0xaf, 0x2c, 0x9c, 0x44, 0x73, 0xcc, 0xe5, 0xd0, 0xd2, 0x2b, 0xd9, },
+            { 0xdf, 0x6a, 0x30, 0x1e, 0x95, 0xc9, 0x5d, 0xad, 0x97, 0xae, 0x0c, 0xc8, 0xc6, 0x91, 0x3b, 0xd8, },
+            { 0x80, 0x11, 0x89, 0x90, 0x2c, 0x85, 0x7f, 0x39, 0xe7, 0x35, 0x91, 0x28, 0x5e, 0x70, 0xb6, 0xdb, },
+            { 0xe6, 0x17, 0x34, 0x6a, 0xc9, 0xc2, 0x31, 0xbb, 0x36, 0x50, 0xae, 0x34, 0xcc, 0xca, 0x0c, 0x5b, },
+            { 0x27, 0xd9, 0x34, 0x37, 0xef, 0xb7, 0x21, 0xaa, 0x40, 0x18, 0x21, 0xdc, 0xec, 0x5a, 0xdf, 0x89, },
+            { 0x89, 0x23, 0x7d, 0x9d, 0xed, 0x9c, 0x5e, 0x78, 0xd8, 0xb1, 0xc9, 0xb1, 0x66, 0xcc, 0x73, 0x42, },
+            { 0x4a, 0x6d, 0x80, 0x91, 0xbf, 0x5e, 0x7d, 0x65, 0x11, 0x89, 0xfa, 0x94, 0xa2, 0x50, 0xb1, 0x4c, },
+            { 0x0e, 0x33, 0xf9, 0x60, 0x55, 0xe7, 0xae, 0x89, 0x3f, 0xfc, 0x0e, 0x3d, 0xcf, 0x49, 0x29, 0x02, },
+            { 0xe6, 0x1c, 0x43, 0x2b, 0x72, 0x0b, 0x19, 0xd1, 0x8e, 0xc8, 0xd8, 0x4b, 0xdc, 0x63, 0x15, 0x1b, },
+            { 0xf7, 0xe5, 0xae, 0xf5, 0x49, 0xf7, 0x82, 0xcf, 0x37, 0x90, 0x55, 0xa6, 0x08, 0x26, 0x9b, 0x16, },
+            { 0x43, 0x8d, 0x03, 0x0f, 0xd0, 0xb7, 0xa5, 0x4f, 0xa8, 0x37, 0xf2, 0xad, 0x20, 0x1a, 0x64, 0x03, },
+            { 0xa5, 0x90, 0xd3, 0xee, 0x4f, 0xbf, 0x04, 0xe3, 0x24, 0x7e, 0x0d, 0x27, 0xf2, 0x86, 0x42, 0x3f, },
+            { 0x5f, 0xe2, 0xc1, 0xa1, 0x72, 0xfe, 0x93, 0xc4, 0xb1, 0x5c, 0xd3, 0x7c, 0xae, 0xf9, 0xf5, 0x38, },
+            { 0x2c, 0x97, 0x32, 0x5c, 0xbd, 0x06, 0xb3, 0x6e, 0xb2, 0x13, 0x3d, 0xd0, 0x8b, 0x3a, 0x01, 0x7c, },
+            { 0x92, 0xc8, 0x14, 0x22, 0x7a, 0x6b, 0xca, 0x94, 0x9f, 0xf0, 0x65, 0x9f, 0x00, 0x2a, 0xd3, 0x9e, },
+            { 0xdc, 0xe8, 0x50, 0x11, 0x0b, 0xd8, 0x32, 0x8c, 0xfb, 0xd5, 0x08, 0x41, 0xd6, 0x91, 0x1d, 0x87, },
+            { 0x67, 0xf1, 0x49, 0x84, 0xc7, 0xda, 0x79, 0x12, 0x48, 0xe3, 0x2b, 0xb5, 0x92, 0x25, 0x83, 0xda, },
+            { 0x19, 0x38, 0xf2, 0xcf, 0x72, 0xd5, 0x4e, 0xe9, 0x7e, 0x94, 0x16, 0x6f, 0xa9, 0x1d, 0x2a, 0x36, },
+            { 0x74, 0x48, 0x1e, 0x96, 0x46, 0xed, 0x49, 0xfe, 0x0f, 0x62, 0x24, 0x30, 0x16, 0x04, 0x69, 0x8e, },
+            { 0x57, 0xfc, 0xa5, 0xde, 0x98, 0xa9, 0xd6, 0xd8, 0x00, 0x64, 0x38, 0xd0, 0x58, 0x3d, 0x8a, 0x1d, },
+            { 0x9f, 0xec, 0xde, 0x1c, 0xef, 0xdc, 0x1c, 0xbe, 0xd4, 0x76, 0x36, 0x74, 0xd9, 0x57, 0x53, 0x59, },
+            { 0xe3, 0x04, 0x0c, 0x00, 0xeb, 0x28, 0xf1, 0x53, 0x66, 0xca, 0x73, 0xcb, 0xd8, 0x72, 0xe7, 0x40, },
+            { 0x76, 0x97, 0x00, 0x9a, 0x6a, 0x83, 0x1d, 0xfe, 0xcc, 0xa9, 0x1c, 0x59, 0x93, 0x67, 0x0f, 0x7a, },
+            { 0x58, 0x53, 0x54, 0x23, 0x21, 0xf5, 0x67, 0xa0, 0x05, 0xd5, 0x47, 0xa4, 0xf0, 0x47, 0x59, 0xbd, },
+            { 0x51, 0x50, 0xd1, 0x77, 0x2f, 0x50, 0x83, 0x4a, 0x50, 0x3e, 0x06, 0x9a, 0x97, 0x3f, 0xbd, 0x7c, },
+        };
+
+        performTest(2, 4, vectors_sip128);
+    }
+
+    private void performTest_4_8()
+        throws Exception
+    {
+        int[][] vectors_sip128 = new int[][] {
+            { 0x1f, 0x64, 0xce, 0x58, 0x6d, 0xa9, 0x04, 0xe9, 0xcf, 0xec, 0xe8, 0x54, 0x83, 0xa7, 0x0a, 0x6c, },
+            { 0x47, 0x34, 0x5d, 0xa8, 0xef, 0x4c, 0x79, 0x47, 0x6a, 0xf2, 0x7c, 0xa7, 0x91, 0xc7, 0xa2, 0x80, },
+            { 0xe1, 0x49, 0x5f, 0xa3, 0x96, 0xca, 0x2d, 0xc6, 0x22, 0x73, 0x81, 0x5f, 0x18, 0x82, 0x21, 0xa4, },
+            { 0xc7, 0xa2, 0x73, 0x84, 0x4a, 0xc5, 0x4e, 0x83, 0x5a, 0x9c, 0xb6, 0x7f, 0x81, 0x05, 0x76, 0x02, },
+            { 0x54, 0x1f, 0x52, 0xbb, 0xf4, 0x3e, 0xce, 0x4e, 0x2a, 0x95, 0xc8, 0xe0, 0x1f, 0x65, 0x6d, 0xef, },
+            { 0x17, 0x97, 0x3b, 0xd4, 0x0d, 0xf3, 0x48, 0x15, 0x24, 0x4f, 0x99, 0x0c, 0xbf, 0x12, 0xbe, 0x5d, },
+            { 0x6b, 0x0b, 0x36, 0x0d, 0x56, 0x32, 0x80, 0xcd, 0xb1, 0x7d, 0x56, 0xc9, 0x08, 0xe1, 0xf5, 0xff, },
+            { 0xed, 0x00, 0xe1, 0x3b, 0x18, 0x4b, 0xf1, 0xc2, 0x72, 0x6b, 0x8b, 0x54, 0xff, 0xd2, 0xee, 0xe0, },
+            { 0xa7, 0xd9, 0x46, 0x13, 0x8f, 0xf9, 0xed, 0xf5, 0x36, 0x4a, 0x5a, 0x23, 0xaf, 0xca, 0xe0, 0x63, },
+            { 0x9e, 0x73, 0x14, 0xb7, 0x54, 0x5c, 0xec, 0xa3, 0x8b, 0x9a, 0x55, 0x49, 0xe4, 0xfb, 0x0b, 0xe8, },
+            { 0x58, 0x6c, 0x62, 0xc6, 0x84, 0x89, 0xd1, 0x68, 0xae, 0xe6, 0x5b, 0x88, 0x9a, 0xb9, 0x12, 0x75, },
+            { 0xe6, 0x71, 0x52, 0xa6, 0x4c, 0xa3, 0xd1, 0x47, 0xc4, 0xab, 0x84, 0x1e, 0x2f, 0x2e, 0x7a, 0x99, },
+            { 0x7f, 0x1c, 0x7a, 0xea, 0x90, 0x8d, 0xe5, 0x2e, 0x3e, 0x9e, 0x08, 0x83, 0xee, 0xa8, 0x16, 0xaf, },
+            { 0xde, 0x82, 0x7a, 0xbf, 0x92, 0xb7, 0x33, 0x92, 0x3f, 0x35, 0x33, 0x0d, 0xb5, 0xef, 0x4a, 0x34, },
+            { 0x59, 0x75, 0x63, 0x64, 0x0f, 0x37, 0x9a, 0xc5, 0x37, 0x67, 0x8e, 0xe2, 0x35, 0x4c, 0x7d, 0xf9, },
+            { 0x28, 0x4d, 0x03, 0x30, 0x3a, 0x45, 0x3a, 0x59, 0x3d, 0x78, 0xf7, 0xfa, 0xdc, 0x90, 0x62, 0xcb, },
+            { 0x91, 0x4a, 0xc7, 0xa2, 0x59, 0x7f, 0x63, 0xb7, 0xc0, 0xfd, 0xe5, 0xab, 0x8d, 0x4e, 0xad, 0x9c, },
+            { 0x0d, 0x51, 0x15, 0xa4, 0x4b, 0xa4, 0x55, 0xee, 0x3a, 0x45, 0x3b, 0x95, 0xce, 0x87, 0xc3, 0xcb, },
+            { 0x54, 0x9b, 0x93, 0x9d, 0x0b, 0xf1, 0xd8, 0x94, 0x83, 0x37, 0x88, 0x5a, 0x84, 0xce, 0x79, 0x14, },
+            { 0x6c, 0x17, 0x97, 0x69, 0xcd, 0x34, 0x8a, 0xeb, 0xd2, 0xfb, 0x13, 0x57, 0x8c, 0x72, 0xb4, 0x6c, },
+            { 0xaa, 0xd0, 0x36, 0xc1, 0x38, 0xc9, 0x57, 0xe0, 0x68, 0x2a, 0x00, 0xee, 0x2f, 0x86, 0x40, 0x8b, },
+            { 0x21, 0xb1, 0xee, 0xc4, 0x2f, 0xb6, 0x70, 0xbf, 0xee, 0x90, 0x44, 0xff, 0x4e, 0xd7, 0x3a, 0x26, },
+            { 0x05, 0x93, 0xa1, 0xd6, 0x29, 0x97, 0xed, 0x37, 0x46, 0x53, 0xc9, 0x17, 0x46, 0x3f, 0x14, 0xeb, },
+            { 0x11, 0x3d, 0x31, 0x62, 0x77, 0x19, 0xf9, 0x1e, 0xa0, 0xf1, 0xff, 0xc6, 0x86, 0x57, 0xe2, 0x4e, },
+            { 0xb3, 0x39, 0x4c, 0xf7, 0x2d, 0xe0, 0x6a, 0xdd, 0x0e, 0x73, 0x14, 0xf0, 0xc2, 0x52, 0xc4, 0xd6, },
+            { 0x92, 0x2a, 0x98, 0xda, 0x9d, 0x35, 0xc3, 0x41, 0xe2, 0x45, 0x6b, 0xe4, 0xcd, 0x63, 0x89, 0xd2, },
+            { 0x59, 0x6b, 0x62, 0x30, 0xf7, 0x57, 0xb3, 0x4a, 0xa2, 0xdc, 0xea, 0x50, 0xcb, 0xb2, 0x8d, 0x4d, },
+            { 0xc2, 0x4e, 0xe4, 0x97, 0xd5, 0x5b, 0x7e, 0x80, 0x06, 0x84, 0xdf, 0x75, 0x65, 0x59, 0xee, 0x48, },
+            { 0x5e, 0x9c, 0xb6, 0xa1, 0x36, 0x68, 0x1e, 0xd4, 0x5e, 0x2b, 0x9d, 0xe4, 0xdc, 0x01, 0x81, 0x77, },
+            { 0xbf, 0xfa, 0x39, 0xca, 0x86, 0x56, 0xd3, 0x04, 0x79, 0x33, 0xed, 0xfe, 0x9d, 0x81, 0x78, 0xb2, },
+            { 0x18, 0x22, 0x94, 0x18, 0xa1, 0xd0, 0x79, 0x5a, 0x35, 0x7a, 0x80, 0x3a, 0x81, 0x34, 0xae, 0xa3, },
+            { 0x4a, 0x3e, 0x96, 0xff, 0x53, 0x47, 0x4e, 0x2e, 0x73, 0x7b, 0x69, 0x57, 0x1a, 0x77, 0xb0, 0x6e, },
+            { 0xfe, 0xd5, 0xf0, 0xf9, 0xd0, 0x37, 0x72, 0x84, 0x2e, 0x2f, 0x57, 0x2f, 0x63, 0xf1, 0x94, 0x50, },
+            { 0x39, 0x33, 0x58, 0x86, 0xc1, 0xf9, 0x42, 0x63, 0xc4, 0x0c, 0x66, 0x29, 0xc6, 0xbc, 0x44, 0x6f, },
+            { 0xee, 0xa5, 0xf9, 0x3b, 0xb3, 0x87, 0x10, 0xb0, 0x8b, 0x2c, 0x46, 0x97, 0x19, 0x8b, 0xbf, 0x9f, },
+            { 0x80, 0x6e, 0xc7, 0xb6, 0x70, 0x4f, 0x72, 0x0e, 0x37, 0x43, 0x12, 0x06, 0x61, 0x66, 0xd4, 0x3a, },
+            { 0x6e, 0x69, 0xed, 0x9d, 0xf0, 0xc9, 0x39, 0xb4, 0x9d, 0xaf, 0xee, 0xae, 0x60, 0x47, 0xb2, 0xa2, },
+            { 0x93, 0xc7, 0x7b, 0xf2, 0x98, 0xb6, 0xf9, 0xc7, 0x94, 0xa2, 0x30, 0x17, 0x7f, 0x2f, 0xd7, 0x38, },
+            { 0xff, 0xad, 0x9c, 0xd9, 0x8c, 0x2a, 0xa8, 0x75, 0xda, 0xff, 0x3a, 0x2a, 0x4c, 0xe6, 0x0c, 0xe6, },
+            { 0x4d, 0x99, 0x2f, 0xfd, 0xf9, 0x4a, 0x93, 0xcd, 0xcd, 0x64, 0xef, 0x76, 0x57, 0xf5, 0x10, 0xe3, },
+            { 0x32, 0x70, 0x62, 0x4e, 0x24, 0xe0, 0xa1, 0x1e, 0xa1, 0x86, 0xe0, 0x96, 0xbe, 0x1b, 0xce, 0x9b, },
+            { 0x31, 0xe8, 0xbb, 0xe0, 0xcb, 0x4e, 0xff, 0x51, 0x1f, 0xff, 0xc7, 0xc4, 0x09, 0x34, 0x31, 0x77, },
+            { 0xcb, 0xe1, 0x7d, 0x05, 0x87, 0x9a, 0xd9, 0x07, 0x64, 0x8a, 0x12, 0xa0, 0x70, 0x16, 0xab, 0x5b, },
+            { 0x88, 0x48, 0xd4, 0x43, 0x70, 0xe9, 0x8b, 0xe2, 0xd5, 0xd2, 0x8b, 0x46, 0x36, 0x6a, 0x0a, 0xfc, },
+            { 0xb7, 0xff, 0xd1, 0xb2, 0x42, 0x10, 0x76, 0xa9, 0x0c, 0xb5, 0xcf, 0x65, 0x54, 0x09, 0x5e, 0x0c, },
+            { 0x6a, 0x6b, 0x66, 0x6c, 0xd5, 0x23, 0xa8, 0xf6, 0xbb, 0xd8, 0x84, 0xfe, 0x1f, 0xd1, 0x05, 0x0c, },
+            { 0xa8, 0xfe, 0x8a, 0x83, 0x50, 0xfb, 0xf5, 0xc8, 0x05, 0xf1, 0x8c, 0xbd, 0x30, 0x13, 0x62, 0x24, },
+            { 0xcc, 0xe7, 0x11, 0x7a, 0xee, 0x82, 0x36, 0xf2, 0xeb, 0x3a, 0x96, 0x94, 0xd5, 0x7e, 0x62, 0xb5, },
+            { 0x3a, 0x25, 0xf0, 0xe4, 0xfc, 0x28, 0xb7, 0x0c, 0x6b, 0x30, 0x90, 0xba, 0xfe, 0xf6, 0x9f, 0x04, },
+            { 0x3f, 0x05, 0xe6, 0x26, 0x74, 0x9f, 0xc4, 0x8b, 0x81, 0x06, 0xf8, 0xe4, 0x44, 0x31, 0xdd, 0x4a, },
+            { 0x76, 0x68, 0x79, 0xf9, 0x76, 0x72, 0x16, 0x5c, 0x0a, 0xff, 0xd5, 0xfa, 0xdc, 0x77, 0x34, 0x5b, },
+            { 0x43, 0x71, 0xa0, 0x5a, 0xb6, 0x6c, 0x59, 0x8b, 0xc9, 0xc2, 0x84, 0x94, 0xa1, 0xdd, 0x2f, 0x0e, },
+            { 0x65, 0xf8, 0x5b, 0xd3, 0xa2, 0xa5, 0xf1, 0xba, 0x1f, 0x22, 0xb6, 0xef, 0xd6, 0xe0, 0x02, 0x66, },
+            { 0x76, 0xcf, 0x61, 0xda, 0xe5, 0x4b, 0x22, 0xef, 0xca, 0x6a, 0x9f, 0x22, 0x8a, 0xaf, 0x66, 0x11, },
+            { 0x6c, 0xdc, 0xc2, 0xe3, 0x9f, 0xdb, 0xa2, 0x9f, 0x88, 0x53, 0x90, 0xab, 0x9d, 0xa4, 0x84, 0xda, },
+            { 0xe1, 0xee, 0xac, 0xea, 0xcc, 0x3b, 0x67, 0xb2, 0xd8, 0xe4, 0xe2, 0x61, 0x7b, 0x2f, 0xaa, 0x5a, },
+            { 0x0b, 0xd2, 0x9f, 0x6f, 0x4c, 0xe1, 0x0f, 0x17, 0x78, 0xd6, 0xb0, 0x2e, 0xd5, 0xab, 0x5a, 0x6d, },
+            { 0xad, 0x18, 0x9f, 0x15, 0x6a, 0x52, 0x26, 0x7c, 0xe0, 0x87, 0x45, 0x83, 0x5b, 0x65, 0xa6, 0x07, },
+            { 0x0f, 0x6b, 0x99, 0x71, 0x72, 0x25, 0x66, 0xd4, 0x3d, 0xec, 0x6b, 0x99, 0xe3, 0x1c, 0x21, 0x8f, },
+            { 0xa1, 0xa4, 0xc8, 0xfa, 0x4f, 0x3d, 0xf4, 0x66, 0xd3, 0xf3, 0x9c, 0x6f, 0x3d, 0x9e, 0x1a, 0x74, },
+            { 0x3b, 0x1a, 0x3d, 0xb8, 0x8c, 0xf0, 0xc2, 0x1f, 0xc1, 0xa6, 0xd8, 0xa7, 0x2d, 0x9e, 0xf9, 0x1d, },
+            { 0xd1, 0x48, 0x68, 0x02, 0xef, 0xc0, 0x00, 0x28, 0x56, 0xc3, 0x63, 0x5a, 0x8a, 0x69, 0x2e, 0xe5, },
+            { 0xee, 0xa1, 0x5f, 0x8f, 0x7c, 0xae, 0x19, 0x99, 0xfd, 0x56, 0x49, 0x31, 0xc2, 0x2c, 0x1c, 0x3c, },
+            { 0x63, 0xf5, 0xae, 0x63, 0x28, 0xc4, 0xdb, 0x93, 0x20, 0x79, 0x61, 0xee, 0x90, 0x6b, 0xd4, 0xa5, },
+        };
+
+        performTest(4, 8, vectors_sip128);
+    }
+
+    private void performTest(int cRounds, int dRounds, int[][] testvectorsInt)
+        throws Exception
+    {
+        int n = testvectorsInt.length;
+        int macSize = testvectorsInt[0].length;
+
+        byte[][] testvectors = new byte[n][];
+        for (int i = 0; i < n; i++) {
+            testvectors[i] = new byte[macSize];
+            for (int j = 0; j < macSize; j++) {
+                testvectors[i][j] = (byte) testvectorsInt[i][j];
+            }
+        }
+
+        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
+
+        for (int i = 0; i < n; i++) {
+            byte[] input = new byte[i];
+            for (int j = 0; j < input.length; j++) {
+                input[j] = (byte) j;
+            }
+
+            runMAC(cRounds, dRounds, testvectors[i], key, input);
+        }
+
+        SecureRandom random = new SecureRandom();
+        for (int i = 0; i < 100; ++i)
+        {
+            randomTest(cRounds, dRounds, random);
+        }
+    }
+
+    private void runMAC(int cRounds, int dRounds, byte[] expected, byte[] key, byte[] input)
+        throws Exception {
+      runMAC(cRounds, dRounds, expected, key, input, UPDATE_BYTES);
+      runMAC(cRounds, dRounds, expected, key, input, UPDATE_FULL);
+      runMAC(cRounds, dRounds, expected, key, input, UPDATE_MIX);
+    }
+
+    private void runMAC(int cRounds, int dRounds, byte[] expected, byte[] key, byte[] input,
+            int updateType)
+        throws Exception
+    {
+        SipHash128 mac = new SipHash128(cRounds, dRounds);
+        mac.init(new KeyParameter(key));
+
+        updateMAC(mac, input, updateType);
+
+        byte[] output = new byte[mac.getMacSize()];
+        int len = mac.doFinal(output, 0);
+        if (len != output.length)
+        {
+            fail("Result length does not equal getMacSize() for doFinal(byte[],int)");
+        }
+        if (!areEqual(expected, output))
+        {
+            fail("Result does not match expected value for doFinal(byte[],int)");
+        }
+    }
+
+    private void randomTest(int cRounds, int dRounds, SecureRandom random)
+    {
+        byte[] key = new byte[16];
+        random.nextBytes(key);
+
+        int length = 1 + RNGUtils.nextInt(random, 1024);
+        byte[] input = new byte[length];
+        random.nextBytes(input);
+
+        SipHash128 mac = new SipHash128(cRounds, dRounds);
+        mac.init(new KeyParameter(key));
+
+        updateMAC(mac, input, UPDATE_BYTES);
+        byte[] result1 = new byte[16];
+        mac.doFinal(result1, 0);
+
+        updateMAC(mac, input, UPDATE_FULL);
+        byte[] result2 = new byte[16];
+        mac.doFinal(result2, 0);
+
+        updateMAC(mac, input, UPDATE_MIX);
+        byte[] result3 = new byte[16];
+        mac.doFinal(result3, 0);
+
+        if (!Arrays.areEqual(result1, result2) ||
+            !Arrays.areEqual(result1, result3))
+        {
+            fail("Inconsistent results in random test");
+        }
+    }
+
+    private void updateMAC(SipHash128 mac, byte[] input, int updateType)
+    {
+        switch (updateType)
+        {
+        case UPDATE_BYTES:
+        {
+            for (int i = 0; i < input.length; ++i)
+            {
+                mac.update(input[i]);
+            }
+            break;
+        }
+        case UPDATE_FULL:
+        {
+            mac.update(input, 0, input.length);
+            break;
+        }
+        case UPDATE_MIX:
+        {
+            int step = Math.max(1, input.length / 3);
+            int pos = 0;
+            while (pos < input.length)
+            {
+                mac.update(input[pos++]);
+                int len = Math.min(input.length - pos, step);
+                mac.update(input, pos, len);
+                pos += len;
+            }
+            break;
+        }
+        default:
+            throw new IllegalStateException();
+        }
+    }
+
+    public static void main(String[] args)
+    {
+        runTest(new SipHash128Test());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHashTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHashTest.java
index 2b63f6d..ee7ea15 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHashTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/SipHashTest.java
@@ -4,13 +4,18 @@
 
 import org.bouncycastle.crypto.macs.SipHash;
 import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Pack;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
 /*
- * SipHash test values from "SipHash: a fast short-input PRF", by Jean-Philippe
- * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf), Appendix A.
+ * SipHash test values are taken from the output of the C reference implementations:
+ * section "const uint8_t vectors_sip64[64][8]" of the output of command ./vectors.
+ *
+ * For SipHash-2-4, test values from "SipHash: a fast short-input PRF", by Jean-Philippe
+ * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf), Appendix A
+ * will also be evaluated.
  */
 public class SipHashTest
     extends SimpleTest
@@ -27,39 +32,228 @@
     public void performTest()
         throws Exception
     {
+        performTest_2_4();
+        performTest_4_8();
+    }
+
+    private void performTest_2_4()
+        throws Exception
+    {
+        /* Explicit test of the
+         * SipHash test values from "SipHash: a fast short-input PRF", by Jean-Philippe
+         * Aumasson and Daniel J. Bernstein (https://131002.net/siphash/siphash.pdf), Appendix A.
+         */
         byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
         byte[] input = Hex.decode("000102030405060708090a0b0c0d0e");
+        byte[] expected = Hex.decode("e545be4961ca29a1");
+        runMAC(2, 4, expected, key, input);
 
-        runMAC(key, input, UPDATE_BYTES);
-        runMAC(key, input, UPDATE_FULL);
-        runMAC(key, input, UPDATE_MIX);
-        
+        /* SipHash test values are taken from the output of the C reference implementations:
+         * section "const uint8_t vectors_sip64[64][8]" of the output of command ./vectors.
+         */
+        int[][] vectors_sip64 = new int[][] {
+            { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72, },
+            { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74, },
+            { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d, },
+            { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85, },
+            { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf, },
+            { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18, },
+            { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb, },
+            { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab, },
+            { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93, },
+            { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e, },
+            { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a, },
+            { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4, },
+            { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75, },
+            { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14, },
+            { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7, },
+            { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1, },
+            { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f, },
+            { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69, },
+            { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b, },
+            { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb, },
+            { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe, },
+            { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0, },
+            { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93, },
+            { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8, },
+            { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8, },
+            { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc, },
+            { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17, },
+            { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f, },
+            { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde, },
+            { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6, },
+            { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad, },
+            { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32, },
+            { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71, },
+            { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7, },
+            { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12, },
+            { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15, },
+            { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31, },
+            { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02, },
+            { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca, },
+            { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a, },
+            { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e, },
+            { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad, },
+            { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18, },
+            { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4, },
+            { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9, },
+            { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9, },
+            { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb, },
+            { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0, },
+            { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6, },
+            { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7, },
+            { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee, },
+            { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1, },
+            { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a, },
+            { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81, },
+            { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f, },
+            { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24, },
+            { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7, },
+            { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea, },
+            { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60, },
+            { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66, },
+            { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c, },
+            { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f, },
+            { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5, },
+            { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95, },
+        };
+
+        performTest(2, 4, vectors_sip64);
+    }
+
+    private void performTest_4_8()
+        throws Exception
+    {
+        /* SipHash test values are taken from the output of the C reference implementations:
+         * section "const uint8_t vectors_sip64[64][8]" of the output of command ./vectors.
+         */
+        int[][] vectors_sip64 = new int[][] {
+            { 0x41, 0xda, 0x38, 0x99, 0x2b, 0x05, 0x79, 0xc8, },
+            { 0x51, 0xb8, 0x95, 0x52, 0xf9, 0x14, 0x59, 0xc8, },
+            { 0x92, 0x37, 0x16, 0xf0, 0xbe, 0xdd, 0xc3, 0x33, },
+            { 0x6a, 0x46, 0xd4, 0x7d, 0x65, 0x47, 0xc1, 0x05, },
+            { 0xc2, 0x38, 0x59, 0x2b, 0x4a, 0xc1, 0xfa, 0x48, },
+            { 0xf6, 0xc2, 0xd7, 0xd9, 0xcf, 0x52, 0x47, 0xe1, },
+            { 0x6b, 0xb6, 0xbc, 0x34, 0xc8, 0x35, 0x55, 0x8e, },
+            { 0x47, 0xd7, 0x3f, 0x71, 0x5a, 0xbe, 0xfd, 0x4e, },
+            { 0x20, 0xb5, 0x8b, 0x9c, 0x07, 0x2f, 0xdb, 0x50, },
+            { 0x36, 0x31, 0x9a, 0xf3, 0x5e, 0xe1, 0x12, 0x53, },
+            { 0x48, 0xa9, 0xd0, 0xdb, 0x0a, 0x8d, 0x84, 0x8f, },
+            { 0xcc, 0x69, 0x39, 0x60, 0x36, 0x04, 0x0a, 0x81, },
+            { 0x4b, 0x6d, 0x68, 0x53, 0x7a, 0xa7, 0x97, 0x61, },
+            { 0x29, 0x37, 0x96, 0xe9, 0xf2, 0xc9, 0x50, 0x69, },
+            { 0x88, 0x43, 0x1b, 0xea, 0xa7, 0x62, 0x9a, 0x68, },
+            { 0xe0, 0xa6, 0xa9, 0x7d, 0xd5, 0x89, 0xd3, 0x83, },
+            { 0x55, 0x9c, 0xf5, 0x53, 0x80, 0xb2, 0xac, 0x70, },
+            { 0xd5, 0xb7, 0xc5, 0x11, 0x7a, 0xe3, 0x79, 0x4e, },
+            { 0x5a, 0x3c, 0x45, 0x46, 0x34, 0xad, 0x10, 0x2b, },
+            { 0xc0, 0xa4, 0x80, 0xaf, 0xa3, 0x5a, 0x3d, 0xbc, },
+            { 0x78, 0xc2, 0x27, 0x09, 0xe5, 0x28, 0x4b, 0xc8, },
+            { 0xef, 0x26, 0x70, 0x46, 0x0d, 0xeb, 0xd6, 0x9d, },
+            { 0xd9, 0x76, 0xef, 0x86, 0xa9, 0xd0, 0x84, 0xd8, },
+            { 0xe3, 0xd9, 0x81, 0x18, 0x19, 0xea, 0xd0, 0xe8, },
+            { 0x89, 0x33, 0x3c, 0xb5, 0x3e, 0xea, 0xec, 0x16, },
+            { 0x31, 0x15, 0x6c, 0x5f, 0x64, 0x73, 0x49, 0xc6, },
+            { 0xa5, 0x4c, 0xce, 0x35, 0x35, 0x76, 0x32, 0xa4, },
+            { 0x06, 0x5d, 0x89, 0x25, 0xc0, 0xa7, 0xd2, 0xfe, },
+            { 0x2b, 0xbb, 0xaa, 0x82, 0x22, 0x1a, 0x3a, 0x8b, },
+            { 0x87, 0x0b, 0xfb, 0xce, 0x64, 0x09, 0x7b, 0x70, },
+            { 0x40, 0xd8, 0xe0, 0xf9, 0x64, 0x95, 0xee, 0x8b, },
+            { 0x79, 0xfc, 0xa7, 0xf4, 0x0b, 0xfa, 0xdf, 0x12, },
+            { 0x00, 0x0b, 0xfb, 0xf2, 0x2f, 0x76, 0x9e, 0xd2, },
+            { 0x40, 0x68, 0x55, 0x91, 0xf8, 0xe5, 0x22, 0xfa, },
+            { 0x2b, 0xe6, 0xfe, 0x74, 0xd8, 0x14, 0x9d, 0x0d, },
+            { 0xba, 0x7e, 0x2f, 0x0e, 0x0b, 0x75, 0x60, 0xed, },
+            { 0x02, 0xe9, 0xe3, 0x84, 0xed, 0xa7, 0xe1, 0x97, },
+            { 0xc4, 0xe8, 0x0a, 0x62, 0x95, 0x27, 0x63, 0xb6, },
+            { 0x83, 0x27, 0xed, 0xc6, 0x5d, 0x5c, 0x6d, 0xd3, },
+            { 0x79, 0xfc, 0x64, 0xd1, 0x64, 0xa4, 0x2f, 0xc0, },
+            { 0x15, 0x4a, 0x75, 0x11, 0xcb, 0xfc, 0x61, 0x4e, },
+            { 0x8b, 0x14, 0x8d, 0x7c, 0xec, 0xa0, 0xe6, 0x6f, },
+            { 0xdf, 0xee, 0x69, 0xb6, 0x54, 0xc4, 0x03, 0xfa, },
+            { 0xc5, 0x8f, 0x36, 0xa6, 0x69, 0x7b, 0xb7, 0xc9, },
+            { 0xa6, 0xc5, 0xbe, 0x9c, 0x05, 0xc6, 0x31, 0x21, },
+            { 0xb5, 0x8a, 0x87, 0x59, 0xfb, 0xcd, 0x89, 0x31, },
+            { 0xd7, 0x68, 0x3a, 0x67, 0x04, 0xcc, 0xc4, 0x25, },
+            { 0xcb, 0x6a, 0xe6, 0xe1, 0xe5, 0xa2, 0x44, 0x8d, },
+            { 0x6e, 0x26, 0x69, 0x5b, 0x3a, 0x3a, 0x51, 0x73, },
+            { 0x78, 0x71, 0x07, 0xcf, 0x9f, 0x33, 0xac, 0x4a, },
+            { 0x16, 0x75, 0x90, 0xda, 0xd9, 0x7b, 0x74, 0x84, },
+            { 0x00, 0x6b, 0x68, 0x1e, 0xf0, 0x6b, 0xf3, 0x06, },
+            { 0x1c, 0x9b, 0x30, 0x02, 0x66, 0xef, 0xcf, 0xa6, },
+            { 0x28, 0x8d, 0x2f, 0x88, 0xd1, 0xb0, 0xb3, 0x4b, },
+            { 0xe0, 0x11, 0x06, 0xbd, 0xac, 0xf5, 0x6b, 0xfe, },
+            { 0xc0, 0x10, 0x1f, 0x0e, 0x5b, 0x6e, 0x03, 0x28, },
+            { 0xc3, 0xa7, 0x91, 0x45, 0x5b, 0x1b, 0x1c, 0x0a, },
+            { 0x57, 0x07, 0xaf, 0xe1, 0x9e, 0x0b, 0x3a, 0x0f, },
+            { 0xe6, 0x5a, 0x72, 0x29, 0xfe, 0x53, 0x59, 0x4f, },
+            { 0x00, 0x2f, 0x9d, 0xb9, 0xab, 0x1a, 0xaf, 0x4c, },
+            { 0x59, 0x28, 0xcb, 0x50, 0x44, 0xc1, 0x06, 0x06, },
+            { 0xd5, 0x38, 0x01, 0x96, 0x7b, 0x85, 0x73, 0x21, },
+            { 0x05, 0xdb, 0x36, 0x4f, 0x1a, 0x09, 0x99, 0xcc, },
+            { 0xe6, 0x77, 0x84, 0xbc, 0x55, 0x03, 0xde, 0x23, },
+        };
+
+        performTest(4, 8, vectors_sip64);
+    }
+
+    private void performTest(int cRounds, int dRounds, int[][] testvectorsInt)
+        throws Exception
+    {
+        int n = testvectorsInt.length;
+        int macSize = testvectorsInt[0].length;
+
+        byte[][] testvectors = new byte[n][];
+        for (int i = 0; i < n; i++) {
+            testvectors[i] = new byte[macSize];
+            for (int j = 0; j < macSize; j++) {
+                testvectors[i][j] = (byte) testvectorsInt[i][j];
+            }
+        }
+
+        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
+
+        for (int i = 0; i < n; i++) {
+            byte[] input = new byte[i];
+            for (int j = 0; j < input.length; j++) {
+                input[j] = (byte) j;
+            }
+
+            runMAC(cRounds, dRounds, testvectors[i], key, input);
+        }
+
         SecureRandom random = new SecureRandom();
         for (int i = 0; i < 100; ++i)
         {
-            randomTest(random);
+            randomTest(cRounds, dRounds, random);
         }
     }
 
-    private void runMAC(byte[] key, byte[] input, int updateType)
+    private void runMAC(int cRounds, int dRounds, byte[] expected, byte[] key, byte[] input)
+        throws Exception {
+        runMAC(cRounds, dRounds, expected, key, input, UPDATE_BYTES);
+        runMAC(cRounds, dRounds, expected, key, input, UPDATE_FULL);
+        runMAC(cRounds, dRounds, expected, key, input, UPDATE_MIX);
+    }
+
+    private void runMAC(int cRounds, int dRounds, byte[] expected, byte[] key, byte[] input,
+            int updateType)
         throws Exception
     {
-        long expected = 0xa129ca6149be45e5L;
+        long expectedLong = Pack.littleEndianToLong(expected, 0);
 
-        SipHash mac = new SipHash();
+        SipHash mac = new SipHash(cRounds, dRounds);
         mac.init(new KeyParameter(key));
 
         updateMAC(mac, input, updateType);
 
         long result = mac.doFinal();
-        if (expected != result)
+
+        if (expectedLong != result)
         {
             fail("Result does not match expected value for doFinal()");
         }
 
-        byte[] expectedBytes = new byte[8];
-        Pack.longToLittleEndian(expected, expectedBytes, 0);
-
         updateMAC(mac, input, updateType);
 
         byte[] output = new byte[mac.getMacSize()];
@@ -68,13 +262,13 @@
         {
             fail("Result length does not equal getMacSize() for doFinal(byte[],int)");
         }
-        if (!areEqual(expectedBytes, output))
+        if (!areEqual(expected, output))
         {
             fail("Result does not match expected value for doFinal(byte[],int)");
         }
     }
 
-    private void randomTest(SecureRandom random)
+    private void randomTest(int cRounds, int dRounds, SecureRandom random)
     {
         byte[] key = new byte[16];
         random.nextBytes(key);
@@ -83,19 +277,23 @@
         byte[] input = new byte[length];
         random.nextBytes(input);
 
-        SipHash mac = new SipHash();
+        SipHash mac = new SipHash(cRounds, dRounds);
         mac.init(new KeyParameter(key));
 
         updateMAC(mac, input, UPDATE_BYTES);
-        long result1 = mac.doFinal();
+        byte[] result1 = new byte[16];
+        mac.doFinal(result1, 0);
 
         updateMAC(mac, input, UPDATE_FULL);
-        long result2 = mac.doFinal();
+        byte[] result2 = new byte[16];
+        mac.doFinal(result2, 0);
 
         updateMAC(mac, input, UPDATE_MIX);
-        long result3 = mac.doFinal();
+        byte[] result3 = new byte[16];
+        mac.doFinal(result3, 0);
 
-        if (result1 != result2 || result1 != result3)
+        if (!Arrays.areEqual(result1, result2) ||
+            !Arrays.areEqual(result1, result3))
         {
             fail("Inconsistent results in random test");
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/TEATest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/TEATest.java
index 98b0ec9..0f05237 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/TEATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/TEATest.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * TEA tester - based on C implementation results from http://www.simonshepherd.supanet.com/tea.htm
+ * TEA tester - based on C implementation results from https://www.simonshepherd.supanet.com/tea.htm
  */
 public class TEATest
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/XTEATest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/XTEATest.java
index 2b5279e..33fa0f6 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/test/XTEATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/XTEATest.java
@@ -6,7 +6,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * TEA tester - based on C implementation results from http://www.simonshepherd.supanet.com/tea.htm
+ * TEA tester - based on C implementation results from https://www.simonshepherd.supanet.com/tea.htm
  */
 public class XTEATest
     extends CipherTest
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/test/ZucTest.java b/bcprov/src/main/java/org/bouncycastle/crypto/test/ZucTest.java
new file mode 100644
index 0000000..872f849
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/test/ZucTest.java
@@ -0,0 +1,497 @@
+package org.bouncycastle.crypto.test;
+
+import org.bouncycastle.crypto.Mac;
+import org.bouncycastle.crypto.StreamCipher;
+import org.bouncycastle.crypto.engines.Zuc128CoreEngine;
+import org.bouncycastle.crypto.engines.Zuc128Engine;
+import org.bouncycastle.crypto.engines.Zuc256CoreEngine;
+import org.bouncycastle.crypto.engines.Zuc256Engine;
+import org.bouncycastle.crypto.macs.Zuc128Mac;
+import org.bouncycastle.crypto.macs.Zuc256Mac;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+/**
+ * Test Cases for Zuc128 and Zuc256.
+ * Test Vectors taken from https://www.gsma.com/aboutus/wp-content/uploads/2014/12/eea3eia3zucv16.pdf for Zuc128
+ * and https://www.is.cas.cn/ztzl2016/zouchongzhi/201801/W020180126529970733243.pdf for Zuc256.
+ */
+public class ZucTest
+    extends SimpleTest
+{
+    private static final int INT_SIZE = 32;
+    private static final int BYTE_SIZE = 8;
+    
+    /**
+     * Test Keys and IV.
+     */
+    private static final String KEY128_1 =
+        "00000000000000000000000000000000";
+    private static final String KEY128_2 =
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+    private static final String KEY256_1 =
+        "00000000000000000000000000000000" +
+            "00000000000000000000000000000000";
+    private static final String KEY256_2 =
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
+            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+    private static final String IV128_1 = "00000000000000000000000000000000";
+    private static final String IV128_2 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+    private static final String IV200_1 = "00000000000000000000000000000000000000000000000000";
+    private static final String IV200_2 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F3F3F3F3F3F3F";
+
+    /**
+     * Define the bit limits for engines.
+     */
+    private static final int ZUC256LIMIT = 20000;
+    private static final int ZUC128LIMIT = 65504;
+
+    public String getName()
+    {
+        return "Zuc";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        new Zuc128Test().testTheCipher();
+        new Zuc256Test().testTheCipher();
+        new Zuc128MacTest().testTheMac();
+        new Zuc256Mac32Test().testTheMac();
+        new Zuc256Mac64Test().testTheMac();
+        new Zuc256Mac128Test().testTheMac();
+    }
+
+    /**
+     * The TestCase.
+     */
+    private static class TestCase
+    {
+        /**
+         * The testCase.
+         */
+        private final String theKey;
+        private final String theIV;
+        private final String thePlainText;
+        private final String theExpected;
+
+        /**
+         * Constructor.
+         *
+         * @param pKey      the key
+         * @param pIV       the IV
+         * @param pExpected the expected results.
+         */
+        TestCase(final String pKey,
+                 final String pIV,
+                 final String pExpected)
+        {
+            this(pKey, pIV, null, pExpected);
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param pKey      the key
+         * @param pIV       the IV
+         * @param pPlain    the plainText
+         * @param pExpected the expected results.
+         */
+        TestCase(final String pKey,
+                 final String pIV,
+                 final String pPlain,
+                 final String pExpected)
+        {
+            theKey = pKey;
+            theIV = pIV;
+            thePlainText = pPlain;
+            theExpected = pExpected;
+        }
+    }
+
+    /**
+     * Zuc128.
+     */
+    class Zuc128Test
+    {
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST4 = new TestCase(KEY128_1, IV128_1,
+            "27bede74018082da87d4e5b69f18bf6632070e0f39b7b692b4673edc3184a48e27636f4414510d62cc15cfe194ec4f6d4b8c8fcc630648badf41b6f9d16a36ca"
+        );
+        private final TestCase TEST5 = new TestCase(KEY128_2, IV128_2,
+            "0657cfa07096398b734b6cb4883eedf4257a76eb97595208d884adcdb1cbffb8e0f9d15846a0eed015328503351138f740d079af17296c232c4f022d6e4acac6"
+        );
+
+        /**
+         * Test cipher.
+         */
+        void testTheCipher()
+        {
+            final Zuc128CoreEngine myEngine = new Zuc128Engine();
+            testCipher(myEngine, TEST4);
+            testCipher(myEngine, TEST5);
+            testStreamLimit(myEngine, TEST5, ZUC128LIMIT);
+        }
+    }
+
+    /**
+     * Zuc256.
+     */
+    class Zuc256Test
+    {
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST4 = new TestCase(KEY256_1, IV200_1,
+            "58d03ad62e032ce2dafc683a39bdcb0352a2bc67f1b7de74163ce3a101ef55589639d75b95fa681b7f090df756391ccc903b7612744d544c17bc3fad8b163b08"
+        );
+        private final TestCase TEST5 = new TestCase(KEY256_2, IV200_2,
+            "3356cbaed1a1c18b6baa4ffe343f777c9e15128f251ab65b949f7b26ef7157f296dd2fa9df95e3ee7a5be02ec32ba585505af316c2f9ded27cdbd935e441ce11"
+        );
+
+        /**
+         * Test cipher.
+         */
+        void testTheCipher()
+        {
+            final Zuc256CoreEngine myEngine = new Zuc256Engine();
+            testCipher(myEngine, TEST4);
+            testCipher(myEngine, TEST5);
+            testStreamLimit(myEngine, TEST5, ZUC256LIMIT);
+        }
+    }
+
+    /**
+     * Zuc128Mac.
+     */
+    class Zuc128MacTest
+    {
+
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST1 = new TestCase(KEY128_1, IV128_1,
+            "508dd5ff"
+        );
+        private final TestCase TEST2 = new TestCase(KEY128_1, IV128_1,
+            "fbed4c12"
+        );
+        private final TestCase TEST3 = new TestCase(KEY128_2, IV128_2,
+            "55e01504"
+        );
+        private final TestCase TEST4 = new TestCase(KEY128_2, IV128_2,
+            "9ce9a0c4"
+        );
+
+        /**
+         * Test Mac.
+         */
+        void testTheMac()
+        {
+            final Zuc128Mac myMac = new Zuc128Mac();
+            testMac(myMac, false, TEST1);
+            testMac(myMac, true, TEST2);
+            testMac(myMac, false, TEST3);
+            testMac(myMac, true, TEST4);
+            testMacLimit(myMac, TEST4, ZUC128LIMIT - (2 * INT_SIZE));
+
+            // reset without init().
+            Zuc128Mac xMac = new Zuc128Mac();
+
+            xMac.reset();
+        }
+    }
+
+    /**
+     * Zuc256Mac32.
+     */
+    class Zuc256Mac32Test
+    {
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST1 = new TestCase(KEY256_1, IV200_1,
+            "9b972a74"
+        );
+        private final TestCase TEST2 = new TestCase(KEY256_1, IV200_1,
+            "8754f5cf"
+        );
+        private final TestCase TEST3 = new TestCase(KEY256_2, IV200_2,
+            "1f3079b4"
+        );
+        private final TestCase TEST4 = new TestCase(KEY256_2, IV200_2,
+            "5c7c8b88"
+        );
+
+        /**
+         * Test Mac.
+         */
+        void testTheMac()
+        {
+            final Zuc256Mac myMac = new Zuc256Mac(32);
+            testMac(myMac, false, TEST1);
+            testMac(myMac, true, TEST2);
+            testMac(myMac, false, TEST3);
+            testMac(myMac, true, TEST4);
+            testMacLimit(myMac, TEST4, ZUC256LIMIT - (2 * myMac.getMacSize() * BYTE_SIZE));
+
+            // reset without init().
+            Zuc256Mac xMac = new Zuc256Mac(32);
+
+            xMac.reset();
+        }
+    }
+
+    /**
+     * Zuc256Mac64.
+     */
+    class Zuc256Mac64Test
+    {
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST1 = new TestCase(KEY256_1, IV200_1,
+            "673e54990034d38c"
+        );
+        private final TestCase TEST2 = new TestCase(KEY256_1, IV200_1,
+            "130dc225e72240cc"
+        );
+        private final TestCase TEST3 = new TestCase(KEY256_2, IV200_2,
+            "8c71394d39957725"
+        );
+        private final TestCase TEST4 = new TestCase(KEY256_2, IV200_2,
+            "ea1dee544bb6223b"
+        );
+
+        /**
+         * Test Mac.
+         */
+        void testTheMac()
+        {
+            final Zuc256Mac myMac = new Zuc256Mac(64);
+            testMac(myMac, false, TEST1);
+            testMac(myMac, true, TEST2);
+            testMac(myMac, false, TEST3);
+            testMac(myMac, true, TEST4);
+            testMacLimit(myMac, TEST4, ZUC256LIMIT - (2 * myMac.getMacSize() * BYTE_SIZE));
+        }
+    }
+
+    /**
+     * Zuc256Mac128.
+     */
+    class Zuc256Mac128Test
+    {
+        /**
+         * TestCases.
+         */
+        private final TestCase TEST1 = new TestCase(KEY256_1, IV200_1,
+            "d85e54bbcb9600967084c952a1654b26"
+        );
+        private final TestCase TEST2 = new TestCase(KEY256_1, IV200_1,
+            "df1e8307b31cc62beca1ac6f8190c22f"
+        );
+        private final TestCase TEST3 = new TestCase(KEY256_2, IV200_2,
+            "a35bb274b567c48b28319f111af34fbd"
+        );
+        private final TestCase TEST4 = new TestCase(KEY256_2, IV200_2,
+            "3a83b554be408ca5494124ed9d473205"
+        );
+
+        /**
+         * Test Mac.
+         */
+        void testTheMac()
+        {
+            final Zuc256Mac myMac = new Zuc256Mac(128);
+            testMac(myMac, false, TEST1);
+            testMac(myMac, true, TEST2);
+            testMac(myMac, false, TEST3);
+            testMac(myMac, true, TEST4);
+            testMacLimit(myMac, TEST4, ZUC256LIMIT - (2 * myMac.getMacSize() * BYTE_SIZE));
+        }
+    }
+
+    /**
+     * Test the Cipher against the results.
+     *
+     * @param pCipher   the cipher to test.
+     * @param pTestCase the testCase
+     */
+    void testCipher(final StreamCipher pCipher,
+                    final TestCase pTestCase)
+    {
+        /* Access the expected bytes */
+        final byte[] myExpected = Hex.decode(pTestCase.theExpected);
+
+        /* Create the output buffer */
+        final byte[] myOutput = new byte[myExpected.length];
+
+        /* Access plainText or nulls */
+        final byte[] myData = pTestCase.thePlainText != null
+            ? Hex.decode(pTestCase.thePlainText)
+            : new byte[myExpected.length];
+
+        /* Access the key and the iv */
+        final KeyParameter myKey = new KeyParameter(Hex.decode(pTestCase.theKey));
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+        final ParametersWithIV myParms = new ParametersWithIV(myKey, myIV);
+
+        /* Initialise the cipher and create the keyStream */
+        pCipher.init(true, myParms);
+        pCipher.processBytes(myData, 0, myData.length, myOutput, 0);
+
+        /* Check the encryption */
+        isTrue("Encryption mismatch", Arrays.areEqual(myExpected, myOutput));
+    }
+
+    /**
+     * Test the Mac against the results.
+     *
+     * @param pMac      the mac to test.
+     * @param pOnes     use all ones as data?
+     * @param pTestCase the testCase
+     */
+    void testMac(final Mac pMac,
+                 final boolean pOnes,
+                 final TestCase pTestCase)
+    {
+        /* Access the expected bytes */
+        final byte[] myExpected = Hex.decode(pTestCase.theExpected);
+
+        /* Create the output buffer and the data */
+        final byte[] myOutput = new byte[pMac.getMacSize()];
+        final byte[] myData = new byte[(pOnes ? 4000 : 400) / 8];
+        Arrays.fill(myData, (byte)(pOnes ? 0x11 : 0));
+
+        /* Access the key and the iv */
+        final KeyParameter myKey = new KeyParameter(Hex.decode(pTestCase.theKey));
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+        final ParametersWithIV myParms = new ParametersWithIV(myKey, myIV);
+
+        /* Initialise the cipher and create the keyStream */
+        pMac.init(myParms);
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        /* Check the mac */
+        isTrue("Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+
+        /* Check doFinal reset */
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        isTrue("DoFinal Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+
+        /* Check reset() */
+        pMac.update(myData, 0, myData.length);
+
+        pMac.reset();
+
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        isTrue("Reset Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+    }
+
+    /**
+     * Test the Stream Cipher against the limit.
+     *
+     * @param pCipher   the cipher to test.
+     * @param pTestCase the testCase
+     * @param pLimit    the limit in bits.
+     */
+    void testStreamLimit(final StreamCipher pCipher,
+                         final TestCase pTestCase,
+                         final int pLimit)
+    {
+        /* Check the limit is a whole number of integers */
+        isTrue("Invalid limit", (pLimit % INT_SIZE == 0));
+        final int myNumBytes = pLimit / BYTE_SIZE;
+
+        /* Create the maximum # of bytes */
+        final byte[] myData = new byte[myNumBytes];
+        final byte[] myOutput = new byte[myNumBytes];
+
+        /* Access the key and the iv */
+        final KeyParameter myKey = new KeyParameter(Hex.decode(pTestCase.theKey));
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+        final ParametersWithIV myParms = new ParametersWithIV(myKey, myIV);
+
+        /* Initialise the cipher and create the keyStream */
+        pCipher.init(true, myParms);
+        pCipher.processBytes(myData, 0, myData.length, myOutput, 0);
+
+        /* Check that next encryption throws exception */
+        try
+        {
+            pCipher.processBytes(myData, 0, 1, myOutput, 0);
+            fail("Limit Failure");
+        }
+        catch (IllegalStateException e)
+        {
+            /* OK */
+        }
+    }
+
+    /**
+     * Test the Mac against the limit.
+     *
+     * @param pMac      the mac to test.
+     * @param pTestCase the testCase
+     * @param pLimit    the limit in bits.
+     */
+    void testMacLimit(final Mac pMac,
+                      final TestCase pTestCase,
+                      final int pLimit)
+    {
+        /* Check the limit is a whole numbet of integers */
+        isTrue("Invalid limit", (pLimit % INT_SIZE == 0));
+        final int myNumBytes = pLimit / BYTE_SIZE;
+
+        /* Create the maximum # of bytes */
+        final byte[] myData = new byte[myNumBytes];
+        final byte[] myOutput = new byte[myNumBytes];
+
+        /* Access the key and the iv */
+        final KeyParameter myKey = new KeyParameter(Hex.decode(pTestCase.theKey));
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+        final ParametersWithIV myParms = new ParametersWithIV(myKey, myIV);
+
+        /* Initialise the mac and create the result */
+        pMac.init(myParms);
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        /* Initialise the mac and process as much data as possible */
+        pMac.init(myParms);
+        pMac.update(myData, 0, myData.length);
+
+        /* We expect a failure on processing a further byte */
+        try
+        {
+            pMac.update(myData, 0, 1);
+            pMac.doFinal(myOutput, 0);
+            fail("Limit Failure");
+        }
+        catch (IllegalStateException e)
+        {
+            /* OK */
+        }
+    }
+
+    /**
+     * Main entry point.
+     *
+     * @param args the argyments
+     */
+    public static void main(String[] args)
+    {
+        runTest(new ZucTest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPrivateKeyUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPrivateKeyUtil.java
index d383f80..27aac76 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPrivateKeyUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPrivateKeyUtil.java
@@ -9,6 +9,7 @@
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1TaggedObject;
 import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
 import org.bouncycastle.asn1.sec.ECPrivateKey;
@@ -156,7 +157,7 @@
     {
         AsymmetricKeyParameter result = null;
 
-        if  (blob[0] == 0x30)
+        if (blob[0] == 0x30)
         {
             ASN1Sequence sequence = ASN1Sequence.getInstance(blob);
 
@@ -204,11 +205,7 @@
                         ecPrivateKey.getKey(),
                         new ECNamedDomainParameters(
                             curveOID,
-                            x9Params.getCurve(),
-                            x9Params.getG(),
-                            x9Params.getN(),
-                            x9Params.getH(),
-                            x9Params.getSeed()));
+                            x9Params));
                 }
             }
         }
@@ -254,21 +251,43 @@
             }
 
             String keyType = pkIn.readString();
-            if (!"ssh-ed25519".equals(keyType))
+
+            if ("ssh-ed25519".equals(keyType))
             {
-                throw new IllegalStateException("can not parse private key of type " + keyType);
+                // Public key
+                pkIn.readBlock();
+                // Private key value..
+                byte[] edPrivateKey = pkIn.readBlock();
+                if (edPrivateKey.length != Ed25519PrivateKeyParameters.KEY_SIZE + Ed25519PublicKeyParameters.KEY_SIZE)
+                {
+                    throw new IllegalStateException("private key value of wrong length");
+                }
+
+                result = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
+            }
+            else if (keyType.startsWith("ecdsa"))
+            {
+
+                ASN1ObjectIdentifier oid = SSHNamedCurves.getByName(Strings.fromByteArray(pkIn.readBlock()));
+                if (oid == null)
+                {
+                    throw new IllegalStateException("OID not found for: " + keyType);
+                }
+
+                X9ECParameters curveParams = NISTNamedCurves.getByOID(oid);
+                if (curveParams == null)
+                {
+                    throw new IllegalStateException("Curve not found for: " + oid);
+                }
+
+                // Skip public key.
+                pkIn.readBlock();
+                byte[] privKey = pkIn.readBlock();
+
+                result = new ECPrivateKeyParameters(new BigInteger(1, privKey),
+                    new ECNamedDomainParameters(oid, curveParams));
             }
 
-            // Skip public key
-            pkIn.skipBlock();
-
-            byte[] edPrivateKey = pkIn.readBlock();
-            if (edPrivateKey.length != Ed25519PrivateKeyParameters.KEY_SIZE + Ed25519PublicKeyParameters.KEY_SIZE)
-            {
-                throw new IllegalStateException("private key value of wrong length");
-            }
-
-            result = new Ed25519PrivateKeyParameters(edPrivateKey, 0);
 
             // Comment for private key
             pkIn.skipBlock();
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPublicKeyUtil.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPublicKeyUtil.java
index e807b89..07a8ac2 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPublicKeyUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/OpenSSHPublicKeyUtil.java
@@ -3,17 +3,16 @@
 import java.io.IOException;
 import java.math.BigInteger;
 
-import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.DSAParameters;
 import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
-import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECNamedDomainParameters;
 import org.bouncycastle.crypto.params.ECPublicKeyParameters;
 import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
 import org.bouncycastle.math.ec.ECCurve;
-import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;
 
 
 /**
@@ -81,12 +80,14 @@
         {
             SSHBuilder builder = new SSHBuilder();
 
-            String name = null;
-            if (((ECPublicKeyParameters)cipherParameters).getParameters().getCurve() instanceof SecP256R1Curve)
-            {
-                name = "nistp256";
-            }
-            else
+            //
+            // checked for named curve parameters..
+            //
+
+
+            String name = SSHNamedCurves.getNameForParameters(((ECPublicKeyParameters)cipherParameters).getParameters());
+
+            if (name == null)
             {
                 throw new IllegalArgumentException("unable to derive ssh curve name for " + ((ECPublicKeyParameters)cipherParameters).getParameters().getCurve().getClass().getName());
             }
@@ -149,35 +150,22 @@
         else if (magic.startsWith(ECDSA))
         {
             String curveName = buffer.readString();
-            String nameToFind = curveName;
 
-            if (curveName.startsWith("nist"))
-            {
-                //
-                // NIST names like P-256 are encoded in SSH as nistp256
-                //
-
-                nameToFind = curveName.substring(4);
-                nameToFind = nameToFind.substring(0, 1) + "-" + nameToFind.substring(1);
-            }
-
-            X9ECParameters x9ECParameters = ECNamedCurveTable.getByName(nameToFind);
+            ASN1ObjectIdentifier oid = SSHNamedCurves.getByName(curveName);
+            X9ECParameters x9ECParameters = SSHNamedCurves.getParameters(oid);
 
             if (x9ECParameters == null)
             {
-                throw new IllegalStateException("unable to find curve for " + magic + " using curve name " + nameToFind);
+                throw new IllegalStateException("unable to find curve for " + magic + " using curve name " + curveName);
             }
 
-            //
-            // Extract name of digest from magic string value;
-            //
-            //String digest = magic.split("-")[1];
-
             ECCurve curve = x9ECParameters.getCurve();
 
             byte[] pointRaw = buffer.readBlock();
 
-            result = new ECPublicKeyParameters(curve.decodePoint(pointRaw), new ECDomainParameters(curve, x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH(), x9ECParameters.getSeed()));
+            result = new ECPublicKeyParameters(
+                curve.decodePoint(pointRaw),
+                new ECNamedDomainParameters(oid, x9ECParameters));
         }
         else if (ED_25519.equals(magic))
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java
index f7d877d..4eaad6a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/PrivateKeyFactory.java
@@ -156,8 +156,7 @@
                 {
                     x9 = ECNamedCurveTable.getByOID(oid);
                 }
-                dParams = new ECNamedDomainParameters(
-                    oid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+                dParams = new ECNamedDomainParameters(oid, x9);
             }
             else
             {
@@ -199,7 +198,7 @@
             if (p instanceof ASN1Sequence && (ASN1Sequence.getInstance(p).size() == 2 || ASN1Sequence.getInstance(p).size() == 3))
             {
 
-                ECDomainParameters ecP = ECGOST3410NamedCurves.getByOID(gostParams.getPublicKeyParamSet());
+                X9ECParameters ecP = ECGOST3410NamedCurves.getByOIDX9(gostParams.getPublicKeyParamSet());
 
                 ecSpec = new ECGOST3410Parameters(
                     new ECNamedDomainParameters(
@@ -207,15 +206,24 @@
                     gostParams.getPublicKeyParamSet(),
                     gostParams.getDigestParamSet(),
                     gostParams.getEncryptionParamSet());
-                ASN1Encodable privKey = keyInfo.parsePrivateKey();
-                if (privKey instanceof ASN1Integer)
+                ASN1OctetString privEnc = keyInfo.getPrivateKey();
+
+                if (privEnc.getOctets().length == 32 || privEnc.getOctets().length == 64)
                 {
-                    d = ASN1Integer.getInstance(privKey).getPositiveValue();
+                    d = new BigInteger(1, Arrays.reverse(privEnc.getOctets()));
                 }
                 else
                 {
-                    byte[] dVal = Arrays.reverse(ASN1OctetString.getInstance(privKey).getOctets());
-                    d = new BigInteger(1, dVal);
+                    ASN1Encodable privKey = keyInfo.parsePrivateKey();
+                    if (privKey instanceof ASN1Integer)
+                    {
+                        d = ASN1Integer.getInstance(privKey).getPositiveValue();
+                    }
+                    else
+                    {
+                        byte[] dVal = Arrays.reverse(ASN1OctetString.getInstance(privKey).getOctets());
+                        d = new BigInteger(1, dVal);
+                    }
                 }
             }
             else
@@ -226,27 +234,10 @@
                 {
                     ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
                     X9ECParameters ecP = ECNamedCurveTable.getByOID(oid);
-                    if (ecP == null)
-                    {
-                        ECDomainParameters gParam = ECGOST3410NamedCurves.getByOID(oid);
-                        ecSpec = new ECGOST3410Parameters(new ECNamedDomainParameters(
-                            oid,
-                            gParam.getCurve(),
-                            gParam.getG(),
-                            gParam.getN(),
-                            gParam.getH(),
-                            gParam.getSeed()), gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(), gostParams.getEncryptionParamSet());
-                    }
-                    else
-                    {
-                        ecSpec = new ECGOST3410Parameters(new ECNamedDomainParameters(
-                            oid,
-                            ecP.getCurve(),
-                            ecP.getG(),
-                            ecP.getN(),
-                            ecP.getH(),
-                            ecP.getSeed()), gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(), gostParams.getEncryptionParamSet());
-                    }
+
+                    ecSpec = new ECGOST3410Parameters(new ECNamedDomainParameters(oid, ecP),
+                        gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(),
+                        gostParams.getEncryptionParamSet());
                 }
                 else if (params.isImplicitlyCA())
                 {
@@ -255,13 +246,9 @@
                 else
                 {
                     X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
-                    ecSpec = new ECGOST3410Parameters(new ECNamedDomainParameters(
-                        algOID,
-                        ecP.getCurve(),
-                        ecP.getG(),
-                        ecP.getN(),
-                        ecP.getH(),
-                        ecP.getSeed()), gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(), gostParams.getEncryptionParamSet());
+                    ecSpec = new ECGOST3410Parameters(new ECNamedDomainParameters(algOID, ecP),
+                        gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(),
+                        gostParams.getEncryptionParamSet());
                 }
 
                 ASN1Encodable privKey = keyInfo.parsePrivateKey();
@@ -273,7 +260,7 @@
                 }
                 else
                 {
-                    org.bouncycastle.asn1.sec.ECPrivateKey ec = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(privKey);
+                    ECPrivateKey ec = ECPrivateKey.getInstance(privKey);
 
                     d = ec.getKey();
                 }
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java
index 864d54e..c432c6a 100644
--- a/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/PublicKeyFactory.java
@@ -280,8 +280,7 @@
                 {
                     x9 = ECNamedCurveTable.getByOID(oid);
                 }
-                dParams = new ECNamedDomainParameters(
-                    oid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+                dParams = new ECNamedDomainParameters(oid, x9);
             }
             else if (params.isImplicitlyCA())
             {
@@ -290,8 +289,7 @@
             else
             {
                 X9ECParameters x9 = X9ECParameters.getInstance(params.getParameters());
-                dParams = new ECDomainParameters(
-                    x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed());
+                dParams = new ECDomainParameters(x9);
             }
 
             DERBitString bits = keyInfo.getPublicKeyData();
@@ -336,7 +334,7 @@
             ASN1ObjectIdentifier publicKeyParamSet = gostParams.getPublicKeyParamSet();
 
             ECGOST3410Parameters ecDomainParameters = new ECGOST3410Parameters(
-                new ECNamedDomainParameters(publicKeyParamSet, ECGOST3410NamedCurves.getByOID(publicKeyParamSet)),
+                new ECNamedDomainParameters(publicKeyParamSet, ECGOST3410NamedCurves.getByOIDX9(publicKeyParamSet)),
                 publicKeyParamSet,
                 gostParams.getDigestParamSet(),
                 gostParams.getEncryptionParamSet());
@@ -385,7 +383,7 @@
             ASN1ObjectIdentifier publicKeyParamSet = gostParams.getPublicKeyParamSet();
 
             ECGOST3410Parameters ecDomainParameters = new ECGOST3410Parameters(
-                new ECNamedDomainParameters(publicKeyParamSet, ECGOST3410NamedCurves.getByOID(publicKeyParamSet)),
+                new ECNamedDomainParameters(publicKeyParamSet, ECGOST3410NamedCurves.getByOIDX9(publicKeyParamSet)),
                 publicKeyParamSet,
                 gostParams.getDigestParamSet(),
                 gostParams.getEncryptionParamSet());
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/util/SSHNamedCurves.java b/bcprov/src/main/java/org/bouncycastle/crypto/util/SSHNamedCurves.java
new file mode 100644
index 0000000..25d0c6c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/crypto/util/SSHNamedCurves.java
@@ -0,0 +1,130 @@
+package org.bouncycastle.crypto.util;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTNamedCurves;
+import org.bouncycastle.asn1.sec.SECObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.ec.CustomNamedCurves;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECNamedDomainParameters;
+import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Strings;
+
+public class SSHNamedCurves
+{
+    private static final Map<ASN1ObjectIdentifier, String> oidToName;
+    private static final Map<String, ASN1ObjectIdentifier> oidMap =
+        Collections.unmodifiableMap(new HashMap<String, ASN1ObjectIdentifier>()
+        {
+            {
+                put("nistp256", SECObjectIdentifiers.secp256r1);
+                put("nistp384", SECObjectIdentifiers.secp384r1);
+                put("nistp521", SECObjectIdentifiers.secp521r1);
+                put("nistk163", SECObjectIdentifiers.sect163k1);
+                put("nistp192", SECObjectIdentifiers.secp192r1);
+                put("nistp224", SECObjectIdentifiers.secp224r1);
+                put("nistk233", SECObjectIdentifiers.sect233k1);
+                put("nistb233", SECObjectIdentifiers.sect233r1);
+                put("nistk283", SECObjectIdentifiers.sect283k1);
+                put("nistk409", SECObjectIdentifiers.sect409k1);
+                put("nistb409", SECObjectIdentifiers.sect409r1);
+                put("nistt571", SECObjectIdentifiers.sect571k1);
+            }
+        });
+
+    private static final Map<String, String> curveNameToSSHName = Collections.unmodifiableMap(new HashMap<String, String>()
+    {
+        {
+            String[][] curves = {
+                {"secp256r1", "nistp256"},
+                {"secp384r1", "nistp384"},
+                {"secp521r1", "nistp521"},
+                {"sect163k1", "nistk163"},
+                {"secp192r1", "nistp192"},
+                {"secp224r1", "nistp224"},
+                {"sect233k1", "nistk233"},
+                {"sect233r1", "nistb233"},
+                {"sect283k1", "nistk283"},
+                {"sect409k1", "nistk409"},
+                {"sect409r1", "nistb409"},
+                {"sect571k1", "nistt571"}
+            };
+            for (int i = 0; i != curves.length; i++)
+            {
+                String[] item = curves[i];
+                put(item[0], item[1]);
+            }
+        }
+    });
+    private static HashMap<ECCurve, String> curveMap = new HashMap<ECCurve, String>()
+    {
+        {
+            Enumeration<Object> e = CustomNamedCurves.getNames();
+            while (e.hasMoreElements())
+            {
+                String name = (String)e.nextElement();
+                X9ECParameters parameters = CustomNamedCurves.getByName(name);
+                put(parameters.getCurve(), name);
+            }
+
+        }
+    };
+
+    static
+    {
+        oidToName = Collections.unmodifiableMap(new HashMap<ASN1ObjectIdentifier, String>()
+        {
+            {
+                for (Iterator it = oidMap.keySet().iterator(); it.hasNext();)
+                {
+                    String key = (String)it.next();
+                    put(oidMap.get(key), key);
+                }
+            }
+        });
+
+
+    }
+
+    public static ASN1ObjectIdentifier getByName(String sshName)
+    {
+        return (ASN1ObjectIdentifier)oidMap.get(sshName);
+    }
+
+    public static X9ECParameters getParameters(String sshName)
+    {
+        return NISTNamedCurves.getByOID((ASN1ObjectIdentifier)oidMap.get(Strings.toLowerCase(sshName)));
+    }
+
+    public static X9ECParameters getParameters(ASN1ObjectIdentifier oid)
+    {
+        return NISTNamedCurves.getByOID(oid);
+    }
+
+    public static String getName(ASN1ObjectIdentifier oid)
+    {
+        return (String)oidToName.get(oid);
+    }
+
+    public static String getNameForParameters(ECDomainParameters parameters)
+    {
+        if (parameters instanceof ECNamedDomainParameters)
+        {
+            return getName(((ECNamedDomainParameters)parameters).getName());
+        }
+
+
+        return getNameForParameters(parameters.getCurve());
+    }
+
+    public static String getNameForParameters(ECCurve curve)
+    {
+        return (String)curveNameToSSHName.get(curveMap.get(curve));
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/AesCcmCiphertext.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/AesCcmCiphertext.java
new file mode 100644
index 0000000..3831c03
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/AesCcmCiphertext.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     AesCcmCiphertext ::= SEQUENCE {
+ *         nonce OCTET STRING (SIZE (12))
+ *         ccmCiphertext Opaque -- 16 bytes longer than plaintext
+ *     }
+ * </pre>
+ */
+public class AesCcmCiphertext
+    extends ASN1Object
+{
+    private final byte[] nonce;
+    private final SequenceOfOctetString opaque;
+
+    private AesCcmCiphertext(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("sequence not length 2");
+        }
+
+        nonce = Utils.octetStringFixed(ASN1OctetString.getInstance(seq.getObjectAt(0)).getOctets(), 12);
+        opaque = SequenceOfOctetString.getInstance(seq.getObjectAt(1));
+    }
+
+    public static AesCcmCiphertext getInstance(Object o)
+    {
+        if (o instanceof AesCcmCiphertext)
+        {
+            return (AesCcmCiphertext)o;
+        }
+        else if (o != null)
+        {
+            return new AesCcmCiphertext(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        v.add(new DEROctetString(nonce));
+        v.add(opaque);
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/BitmapSspRange.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/BitmapSspRange.java
new file mode 100644
index 0000000..f3ae9c8
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/BitmapSspRange.java
@@ -0,0 +1,72 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *     BitmapSspRange ::= SEQUENCE {
+ *         sspValue OCTET STRING (SIZE(1..32)),
+ *         sspBitmask OCTET STRING (SIZE(1..32))
+ *     }
+ * </pre>
+ */
+public class BitmapSspRange
+    extends ASN1Object
+{
+    private final byte[] sspValue;
+    private final byte[] sspBitmask;
+
+    private BitmapSspRange(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("expected sequence with sspValue and sspBitmask");
+        }
+
+        sspValue = Utils.octetStringFixed(
+            ASN1OctetString.getInstance(seq.getObjectAt(0)).getOctets());
+        sspBitmask = Utils.octetStringFixed(
+            ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets());
+    }
+
+    public static BitmapSspRange getInstance(Object o)
+    {
+        if (o instanceof BitmapSspRange)
+        {
+            return (BitmapSspRange)o;
+        }
+        else if (o != null)
+        {
+            return new BitmapSspRange(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public byte[] getSspValue()
+    {
+        return Arrays.clone(sspValue);
+    }
+
+    public byte[] getSspBitmask()
+    {
+        return Arrays.clone(sspBitmask);
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector avec = new ASN1EncodableVector();
+
+        avec.add(new DEROctetString(sspValue));
+        avec.add(new DEROctetString(sspBitmask));
+
+        return new DERSequence(avec);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateBase.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateBase.java
new file mode 100644
index 0000000..54bad31
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateBase.java
@@ -0,0 +1,66 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     CertificateBase ::= SEQUENCE {
+ *         version Uint8(3),
+ *         type CertificateType,
+ *         issuer IssuerIdentifier,
+ *         toBeSigned ToBeSignedCertificate,
+ *         signature Signature OPTIONAL
+ *     }
+ * </pre>
+ */
+public class CertificateBase
+    extends ASN1Object
+{
+    private CertificateType type;
+    private byte[] version;
+
+    protected CertificateBase(ASN1Sequence seq)
+    {
+
+    }
+
+    public static CertificateBase getInstance(Object o)
+    {
+        if (o instanceof ImplicitCertificate)
+        {
+            return (ImplicitCertificate)o;
+        }
+        if (o instanceof ExplicitCertificate)
+        {
+            return (ExplicitCertificate)o;
+        }
+
+        if (o != null)
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(o);
+
+            if (seq.getObjectAt(1).equals(CertificateType.Implicit))
+            {
+                return ImplicitCertificate.getInstance(seq);
+            }
+            if (seq.getObjectAt(1).equals(CertificateType.Explicit))
+            {
+                return ExplicitCertificate.getInstance(seq);
+            }
+            throw new IllegalArgumentException("unknown certificate type");
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateType.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateType.java
new file mode 100644
index 0000000..0d7416f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/CertificateType.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * CertificateType ::= ENUMERATED {
+ * explicit,
+ * implicit,
+ * ...
+ * }
+ */
+public class CertificateType
+{
+
+    public static final CertificateType Explicit = new CertificateType(0);
+    public static final CertificateType Implicit = new CertificateType(1);
+    private final ASN1Enumerated enumerated;
+
+    protected CertificateType(int ordinal)
+    {
+        enumerated = new ASN1Enumerated(ordinal);
+    }
+
+    private CertificateType(ASN1Enumerated enumerated)
+    {
+        this.enumerated = enumerated;
+    }
+
+    public CertificateType getInstance(Object src)
+    {
+        if (src == null)
+        {
+            return null;
+        }
+        else if (src instanceof CertificateType)
+        {
+            return (CertificateType)src;
+        }
+        else
+        {
+            return new CertificateType(ASN1Enumerated.getInstance(src));
+        }
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return enumerated;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/CircularRegion.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/CircularRegion.java
new file mode 100644
index 0000000..e5f1890
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/CircularRegion.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+/**
+ * <pre>
+ *     CircularRegion ::= SEQUENCE {
+ *         center TwoDLocation,
+ *         radius Uint16
+ *     }
+ * </pre>
+ */
+public class CircularRegion
+    extends ASN1Object
+{
+    private CircularRegion(ASN1Sequence seq)
+    {
+
+    }
+
+    public static CircularRegion getInstance(Object o)
+    {
+        if (o instanceof CircularRegion)
+        {
+            return (CircularRegion)o;
+        }
+        else if (o != null)
+        {
+            return new CircularRegion(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Duration.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Duration.java
new file mode 100644
index 0000000..e29736e
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Duration.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     Duration ::= CHOICE {
+ *         microseconds Uint16,
+ *         milliseconds Uint16,
+ *         seconds Uint16,
+ *         minutes Uint16,
+ *         hours Uint16,
+ *         sixtyHours Uint16,
+ *         years Uint16
+ *     }
+ * </pre>
+ */
+public class Duration
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/EncryptedData.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/EncryptedData.java
new file mode 100644
index 0000000..d739e61
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/EncryptedData.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+
+/**
+ * <pre>
+ *     EncryptedData ::= SEQUENCE {
+ *         recipients SequenceOfRecipientInfo,
+ *         ciphertext SymmetricCiphertext
+ *     }
+ * </pre>
+ */
+public class EncryptedData
+{
+    private EncryptedData(ASN1Sequence seq)
+    {
+
+    }
+
+    public static EncryptedData getInstance(Object o)
+    {
+        if (o instanceof EncryptedData)
+        {
+            return (EncryptedData)o;
+        }
+        else if (o != null)
+        {
+            return new EncryptedData(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/EndEntityType.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/EndEntityType.java
new file mode 100644
index 0000000..622f75e
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/EndEntityType.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1BitString;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERBitString;
+
+/**
+ * <pre>
+ *     EndEntityType ::= BIT STRING { app(0), enrol(1) } (SIZE (8)) (ALL EXCEPT ())
+ * </pre>
+ */
+public class EndEntityType
+    extends ASN1Object
+{
+    public static final int        app = (1 << 7);
+    public static final int        enrol = (1 << 6);
+
+    private final ASN1BitString type;
+
+    public EndEntityType(int type)
+    {
+        if (type != app && type != enrol)
+        {
+            throw new IllegalArgumentException("value out of range");
+        }
+
+        this.type = new DERBitString(type);
+    }
+
+    private EndEntityType(DERBitString str)
+    {
+        this.type = str;
+    }
+
+    public static EndEntityType getInstance(Object src)
+    {
+        if (src instanceof EndEntityType)
+        {
+            return (EndEntityType)src;
+        }
+        else if (src != null)
+        {
+            return new EndEntityType(DERBitString.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return type;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/EtsiTs103097Module.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/EtsiTs103097Module.java
new file mode 100644
index 0000000..892afca
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/EtsiTs103097Module.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.its.asn1;
+
+public class EtsiTs103097Module
+{
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ExplicitCertificate.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ExplicitCertificate.java
new file mode 100644
index 0000000..9cd6b30
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ExplicitCertificate.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+
+public class ExplicitCertificate
+    extends CertificateBase
+{
+    private ExplicitCertificate(ASN1Sequence seq)
+    {
+        super(seq);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/GeographicRegion.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/GeographicRegion.java
new file mode 100644
index 0000000..67eb0f1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/GeographicRegion.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     GeographicRegion ::= CHOICE {
+ *         circularRegion CircularRegion,
+ *         rectangularRegion SequenceOfRectangularRegion,
+ *         polygonalRegion PolygonalRegion,
+ *         identifiedRegion SequenceOfIdentifiedRegion,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class GeographicRegion
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/GroupLinkageValue.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/GroupLinkageValue.java
new file mode 100644
index 0000000..0abbe75
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/GroupLinkageValue.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     GroupLinkageValue ::= SEQUENCE {
+ *         jValue OCTET STRING (SIZE(4))
+ *         value OCTET STRING (SIZE(9))
+ *     }
+ * </pre>
+ */
+public class GroupLinkageValue
+    extends ASN1Object
+{
+    private byte[] jValue;
+    private byte[] value;
+
+    private GroupLinkageValue(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("sequence not length 2");
+        }
+
+        jValue = Utils.octetStringFixed(ASN1OctetString.getInstance(seq.getObjectAt(0)).getOctets(), 4);
+        value = Utils.octetStringFixed(ASN1OctetString.getInstance(seq.getObjectAt(1)).getOctets(), 9);
+    }
+
+    public static GroupLinkageValue getInstance(Object src)
+    {
+        if (src instanceof GroupLinkageValue)
+        {
+            return (GroupLinkageValue)src;
+        }
+        else if (src != null)
+        {
+            return new GroupLinkageValue(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public byte[] getJValue()
+    {
+        return jValue;
+    }
+
+    public byte[] getValue()
+    {
+        return value;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector avec = new ASN1EncodableVector();
+        avec.add(new DEROctetString(jValue));
+        avec.add(new DEROctetString(value));
+        return new DERSequence(avec);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/HashAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/HashAlgorithm.java
new file mode 100644
index 0000000..666f606
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/HashAlgorithm.java
@@ -0,0 +1,50 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * CertificateType ::= ENUMERATED {
+ * explicit,
+ * implicit,
+ * ...
+ * }
+ */
+public class HashAlgorithm
+{
+
+    public static final HashAlgorithm sha256 = new HashAlgorithm(0);
+    public static final HashAlgorithm sha384 = new HashAlgorithm(1);
+    private final ASN1Enumerated enumerated;
+
+    protected HashAlgorithm(int ordinal)
+    {
+        enumerated = new ASN1Enumerated(ordinal);
+    }
+
+    private HashAlgorithm(ASN1Enumerated enumerated)
+    {
+        this.enumerated = enumerated;
+    }
+
+    public HashAlgorithm getInstance(Object src)
+    {
+        if (src == null)
+        {
+            return null;
+        }
+        else if (src instanceof HashAlgorithm)
+        {
+            return (HashAlgorithm)src;
+        }
+        else
+        {
+            return new HashAlgorithm(ASN1Enumerated.getInstance(src));
+        }
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return enumerated;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/HashedData.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/HashedData.java
new file mode 100644
index 0000000..52bf3d6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/HashedData.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+
+/**
+ * <pre>
+ *     HashedData ::= CHOICE {
+ *         sha256HashedData OCTET STRING (SIZE(32))
+ *     }
+ * </pre>
+ */
+public class HashedData
+    extends ASN1Object
+    implements ASN1Choice
+{
+    private ASN1OctetString hashData;
+
+    public HashedData(byte[] digest)
+    {
+        this.hashData = new DEROctetString(digest);
+    }
+
+    private HashedData(ASN1OctetString hashData)
+    {
+        this.hashData = hashData;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return hashData;
+    }
+
+    public ASN1OctetString getHashData()
+    {
+        return hashData;
+    }
+
+    public void setHashData(ASN1OctetString hashData)
+    {
+        this.hashData = hashData;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/HeaderInfo.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/HeaderInfo.java
new file mode 100644
index 0000000..28dd743
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/HeaderInfo.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     HeaderInfo ::= SEQUENCE {
+ *         psid Psid,
+ *         generationTime Time64 OPTIONAL,
+ *         expiryTime Time64 OPTIONAL,
+ *         generationLocation ThreeDLocation OPTIONAL,
+ *         p2pcdLearningRequest HashedId3 OPTIONAL,
+ *         missingCrlIdentifier MissingCrlIdentifier OPTIONAL,
+ *         ...,
+ *         inlineP2pcdRequest SequenceOfHashedId3 OPTIONAL,
+ *         requestedCertificate Certificate OPTIONAL
+ *     }
+ * </pre>
+ */
+public class HeaderInfo
+    extends ASN1Object
+{
+    private HeaderInfo(ASN1Sequence seq)
+    {
+
+    }
+
+    public static HeaderInfo getInstance(Object o)
+    {
+        if (o instanceof HeaderInfo)
+        {
+            return (HeaderInfo)o;
+        }
+        else if (o != null)
+        {
+            return new HeaderInfo(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+    
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/IValue.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/IValue.java
new file mode 100644
index 0000000..22897de
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/IValue.java
@@ -0,0 +1,52 @@
+package org.bouncycastle.its.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ * <pre>
+ *     Uint16 ::= INTEGER (0..65535)
+ *
+ *     IValue ::= Uint16
+ * </pre>
+ */
+public class IValue
+    extends ASN1Object
+{
+    private final BigInteger value;
+
+    private IValue(ASN1Integer value)
+    {
+        int i = BigIntegers.intValueExact(value.getValue());
+
+        if (i < 0 || i > 65535)
+        {
+            throw new IllegalArgumentException("value out of range");
+        }
+
+        this.value = value.getValue();
+    }
+
+    public static IValue getInstance(Object src)
+    {
+        if (src instanceof IValue)
+        {
+            return (IValue)src;
+        }
+        else if (src != null)
+        {
+            return new IValue(ASN1Integer.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new ASN1Integer(value);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Content.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Content.java
new file mode 100644
index 0000000..0e88f18
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Content.java
@@ -0,0 +1,46 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     Ieee1609Dot2Content ::= CHOICE {
+ *         unsecuredData Opaque,
+ *         signedData SignedData,
+ *         encryptedData EncryptedData,
+ *         signedCertificateRequest Opaque,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class Ieee1609Dot2Content
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public static Ieee1609Dot2Content getInstance(Object src)
+    {
+        if (src instanceof Ieee1609Dot2Content)
+        {
+            return (Ieee1609Dot2Content)src;
+        }
+        else if (src != null)
+        {
+            // TODO: need choice processing here
+            return getInstance(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Data.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Data.java
new file mode 100644
index 0000000..89ed1c5
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Ieee1609Dot2Data.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.its.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     Ieee1609Dot2Data ::= SEQUENCE {
+ *         protocolVersion Uint8(3),
+ *         content Ieee1609Dot2Content
+ *     }
+ * </pre>
+ */
+public class Ieee1609Dot2Data
+    extends ASN1Object
+{
+    private final BigInteger protcolVersion;
+    private final Ieee1609Dot2Content content;
+
+    private Ieee1609Dot2Data(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("sequence not length 2");
+        }
+
+        protcolVersion = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
+        content = Ieee1609Dot2Content.getInstance(seq.getObjectAt(1));
+    }
+
+    public static Ieee1609Dot2Data getInstance(Object src)
+    {
+        if (src instanceof Ieee1609Dot2Data)
+        {
+            return (Ieee1609Dot2Data)src;
+        }
+        else if (src != null)
+        {
+            return new Ieee1609Dot2Data(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ImplicitCertificate.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ImplicitCertificate.java
new file mode 100644
index 0000000..dfdbad1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ImplicitCertificate.java
@@ -0,0 +1,12 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+
+public class ImplicitCertificate
+    extends CertificateBase
+{
+    private ImplicitCertificate(ASN1Sequence seq)
+    {
+        super(seq);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/IssuerIdentifier.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/IssuerIdentifier.java
new file mode 100644
index 0000000..915cadc
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/IssuerIdentifier.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     IssuerIdentifier ::= CHOICE {
+ *         sha256AndDigest HashedId8,
+ *         self HashAlgorithm,
+ *         ...,
+ *         sha384AndDigest HashedId8
+ *     }
+ * </pre>
+ */
+public class IssuerIdentifier
+    extends ASN1Object
+    implements ASN1Choice
+{
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Latitude.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Latitude.java
new file mode 100644
index 0000000..ad4a8e4
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Latitude.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     Latitude ::= NinetyDegreeInt
+ *
+ *     NinetyDegreeInt ::= INTEGER {
+ *         min (-900000000),
+ *         max (900000000),
+ *         unknown (900000001)
+ *     }
+ * </pre>
+ */
+public class Latitude
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageData.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageData.java
new file mode 100644
index 0000000..e193c9d
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageData.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     LinkageData ::= SEQUENCE {
+ *         iCert IValue,
+ *         linkage-value LinkageValue,
+ *         group-linkage-value GroupLinkageValue OPTIONAL
+ *     }
+ * </pre>
+ */
+public class LinkageData
+    extends ASN1Object
+{
+    private final IValue iCert;
+    private final LinkageValue linkageValue;
+    private final GroupLinkageValue groupLinkageValue;
+
+    private LinkageData(ASN1Sequence seq)
+    {
+        if (seq.size() != 2 && seq.size() != 3)
+        {
+            throw new IllegalArgumentException("sequence must be size 2 or 3");
+        }
+        
+        this.iCert = IValue.getInstance(seq.getObjectAt(2));
+        this.linkageValue = LinkageValue.getInstance(seq.getObjectAt(2));
+        this.groupLinkageValue = GroupLinkageValue.getInstance(seq.getObjectAt(2));
+    }
+
+    public static LinkageData getInstance(Object src)
+    {
+        if (src instanceof LinkageData)
+        {
+            return (LinkageData)src;
+        }
+        else if (src != null)
+        {
+            // TODO: need choice processing here
+            return new LinkageData(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageValue.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageValue.java
new file mode 100644
index 0000000..dffe6a2
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/LinkageValue.java
@@ -0,0 +1,42 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *     LinkageValue ::= OCTET STRING (SIZE(9))
+ * </pre>
+ */
+public class LinkageValue
+    extends ASN1Object
+{
+    private final byte[] value;
+
+    private LinkageValue(ASN1OctetString octs)
+    {
+        this.value = Arrays.clone(Utils.octetStringFixed(octs.getOctets(), 9));
+    }
+
+    public static LinkageValue getInstance(Object src)
+    {
+        if (src instanceof LinkageValue)
+        {
+            return (LinkageValue)src;
+        }
+        else if (src != null)
+        {
+            return new LinkageValue(ASN1OctetString.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DEROctetString(Arrays.clone(value));
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Longitude.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Longitude.java
new file mode 100644
index 0000000..686f83c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Longitude.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     Latitude ::= OneEightyDegreeInt
+ *
+ *     NinetyDegreeInt ::= INTEGER {
+ *         min (-17999999999),
+ *         max (1800000000),
+ *         unknown (1800000001)
+ *     }
+ * </pre>
+ */
+public class Longitude
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/PKRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/PKRecipientInfo.java
new file mode 100644
index 0000000..54ffca1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/PKRecipientInfo.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     PKRecipientInfo ::= SEQUENCE {
+ *         recipientId HashedId8,
+ *         encKey EncryptedDataEncryptionKey
+ *     }
+ * </pre>
+ */
+public class PKRecipientInfo
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/PolygonalRegion.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/PolygonalRegion.java
new file mode 100644
index 0000000..8b95b15
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/PolygonalRegion.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     SEQUENCE SIZE(3..MAX) OF TwoDLocation
+ * </pre>
+ */
+public class PolygonalRegion
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidGroupPermissions.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidGroupPermissions.java
new file mode 100644
index 0000000..2519614
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidGroupPermissions.java
@@ -0,0 +1,59 @@
+package org.bouncycastle.its.asn1;
+
+import java.math.BigInteger;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+/**
+ * <pre>
+ *     PsidGroupPermissions ::= SEQUENCE {
+ *         subjectPermissions SubjectPermissions,
+ *         minChainLength INTEGER DEFAULT 1,
+ *         chainLengthRange INTEGER DEFAULT 0,
+ *         eeType EndEntityType DEFAULT (app)
+ *     }
+ * </pre>
+ */
+public class PsidGroupPermissions
+    extends ASN1Object
+{
+    private final SubjectPermissions subjectPermissions;
+    private final BigInteger minChainLength;
+    private final BigInteger chainLengthRange;
+    private final Object eeType;
+
+    private PsidGroupPermissions(ASN1Sequence seq)
+    {
+        if (seq.size() != 2)
+        {
+            throw new IllegalArgumentException("sequence not length 2");
+        }
+
+        this.subjectPermissions = SubjectPermissions.getInstance(seq.getObjectAt(0));
+        this.minChainLength = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
+        this.chainLengthRange = ASN1Integer.getInstance(seq.getObjectAt(2)).getValue();
+        this.eeType = EndEntityType.getInstance(seq.getObjectAt(3));
+    }
+
+    public static PsidGroupPermissions getInstance(Object src)
+    {
+        if (src instanceof PsidGroupPermissions)
+        {
+            return (PsidGroupPermissions)src;
+        }
+        else if (src != null)
+        {
+            return new PsidGroupPermissions(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidSspRange.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidSspRange.java
new file mode 100644
index 0000000..d48c409
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/PsidSspRange.java
@@ -0,0 +1,89 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * PsidSspRange ::= SEQUENCE {
+ * psid Psid,
+ * sspRange SspRange OPTIONAL
+ * }
+ */
+public class PsidSspRange
+    extends ASN1Object
+{
+    private ASN1Integer psid;
+    private SspRange sspRange;
+
+    public PsidSspRange()
+    {
+
+    }
+
+    public static PsidSspRange getInstance(Object src)
+    {
+        if (src == null)
+        {
+            return null;
+        }
+        else if (src instanceof PsidSspRange)
+        {
+            return (PsidSspRange)src;
+        }
+        else
+        {
+            ASN1Sequence seq = ASN1Sequence.getInstance(src);
+            PsidSspRange psidSspRange = new PsidSspRange();
+            if (seq.size() < 1 || seq.size() > 2)
+            {
+                throw new IllegalStateException("expected sequences with one or optionally two items");
+            }
+
+            if (seq.size() == 1)
+            {
+                psidSspRange.psid = (ASN1Integer)seq.getObjectAt(0);
+            }
+            if (seq.size() == 2)
+            {
+                psidSspRange.sspRange = SspRange.getInstance(seq.getObjectAt(1));
+            }
+            return psidSspRange;
+        }
+    }
+
+
+    public ASN1Integer getPsid()
+    {
+        return psid;
+    }
+
+    public void setPsid(ASN1Integer psid)
+    {
+        this.psid = psid;
+    }
+
+    public SspRange getSspRange()
+    {
+        return sspRange;
+    }
+
+    public void setSspRange(SspRange sspRange)
+    {
+        this.sspRange = sspRange;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector avec = new ASN1EncodableVector();
+        avec.add(psid);
+        if (sspRange != null)
+        {
+            avec.add(sspRange);
+        }
+        return new DERSequence(avec);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/RecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/RecipientInfo.java
new file mode 100644
index 0000000..60062c9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/RecipientInfo.java
@@ -0,0 +1,16 @@
+package org.bouncycastle.its.asn1;
+
+/**
+ * <pre>
+ *     RecipientInfo ::= CHOICE {
+ *         pskRecipInfo PreSharedKeyReicpientInfo,
+ *         symmRecipInfo SymmRecipientInfo,
+ *         certRecipInfo PKRecipientInfo,
+ *         signedDataRecipInfo PKRecipientInfo,
+ *         rekRecipInfo PKRecipientInfo
+ *     }
+ * </pre>
+ */
+public class RecipientInfo
+{
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/RectangularRegion.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/RectangularRegion.java
new file mode 100644
index 0000000..40949b7
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/RectangularRegion.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+/**
+ * <pre>
+ *     RectangularRegion ::= SEQUENCE {
+ *         northWest TwoDLocation,
+ *         southEast TwoDLocation
+ *     }
+ * </pre>
+ */
+public class RectangularRegion
+    extends ASN1Object
+{
+    private RectangularRegion(ASN1Sequence seq)
+    {
+
+    }
+
+    public static RectangularRegion getInstance(Object o)
+    {
+        if (o instanceof RectangularRegion)
+        {
+            return (RectangularRegion)o;
+        }
+        else if (o != null)
+        {
+            return new RectangularRegion(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfCertificate.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfCertificate.java
new file mode 100644
index 0000000..ed7ed8f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfCertificate.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SequenceOfCertificate ::= SEQUENCE OF Certificate
+ * </pre>
+ */
+public class SequenceOfCertificate
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfOctetString.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfOctetString.java
new file mode 100644
index 0000000..3ade2e3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfOctetString.java
@@ -0,0 +1,68 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * <pre>
+ *     SequenceOfOctetString ::= SEQUENCE (SIZE(0..MAX)) OF OCTET STRING (SIZE(0..MAX))
+ * </pre>
+ */
+public class SequenceOfOctetString
+    extends ASN1Object
+{
+    private byte[][] octetStrings;
+
+    private SequenceOfOctetString(ASN1Sequence seq)
+    {
+         this.octetStrings = toByteArrays(seq);
+    }
+
+    public static SequenceOfOctetString getInstance(Object o)
+    {
+        if (o instanceof SequenceOfOctetString)
+        {
+            return (SequenceOfOctetString)o;
+        }
+        else if (o != null)
+        {
+            return new SequenceOfOctetString(ASN1Sequence.getInstance(o));
+        }
+
+        return null;
+    }
+
+    public int size()
+    {
+        return octetStrings.length;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != octetStrings.length; i++)
+        {
+            v.add(new DEROctetString(Arrays.clone(octetStrings[i])));
+        }
+
+        return new DERSequence(v);
+    }
+
+    static byte[][] toByteArrays(ASN1Sequence seq)
+    {
+        byte[][] octetStrings = new byte[seq.size()][];
+        for (int i = 0; i != seq.size(); i++)
+        {
+            octetStrings[i] = ASN1OctetString.getInstance(seq.getObjectAt(i)).getOctets();
+        }
+
+        return octetStrings;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfPsidGroupPermissions.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfPsidGroupPermissions.java
new file mode 100644
index 0000000..3412058
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfPsidGroupPermissions.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SEQUENCE OF PsidGroupPermissions
+ * </pre>
+ */
+public class SequenceOfPsidGroupPermissions
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRecipientInfo.java
new file mode 100644
index 0000000..24e67a9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRecipientInfo.java
@@ -0,0 +1,22 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SequenceOfRecipientInfo ::= SEQUENCE OF RecipientInfo
+ * </pre>
+ */
+public class SequenceOfRecipientInfo
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRectangularRegion.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRectangularRegion.java
new file mode 100644
index 0000000..387b819
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SequenceOfRectangularRegion.java
@@ -0,0 +1,32 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SequenceOfRectangularRegion ::= SEQUENCE OF RectangularRegion
+ * </pre>
+ */
+public class SequenceOfRectangularRegion
+    extends ASN1Object
+{
+    private final RectangularRegion[] sequence;
+
+    private SequenceOfRectangularRegion(ASN1Sequence seq)
+    {
+        this.sequence = new RectangularRegion[seq.size()];
+
+        for (int i = 0; i != seq.size(); i++)
+        {
+            sequence[i] = RectangularRegion.getInstance(seq.getObjectAt(i));
+        }
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return new DERSequence(sequence);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ServiceSpecificPermissions.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ServiceSpecificPermissions.java
new file mode 100644
index 0000000..ec459c1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ServiceSpecificPermissions.java
@@ -0,0 +1,6 @@
+package org.bouncycastle.its.asn1;
+
+public class ServiceSpecificPermissions
+{
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Signature.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Signature.java
new file mode 100644
index 0000000..b03ae50
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Signature.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     Signature ::= CHOICE {
+ *         ecdsaNistP256Signature EcdsaP256Signature,
+ *         ecdsaBrainpoolP256r1Signature EcdsaP256Signature,
+ *         ...
+ *         ecdsaBrainpoolP384r1Signature EcdsaP384Signature
+ *     }
+ * </pre>
+ */
+public class Signature
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedData.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedData.java
new file mode 100644
index 0000000..69fd986
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedData.java
@@ -0,0 +1,27 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SignedData ::= SEQUENCE {
+ *         hashId HashAlgorithm,
+ *         tbsData ToBeSignedData,
+ *         signer SignerIdentifier,
+ *         signature Signature
+ *     }
+ * </pre>
+ */
+public class SignedData
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedDataPayload.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedDataPayload.java
new file mode 100644
index 0000000..6cbd9e2
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignedDataPayload.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SignedDataPayload ::= SEQUENCE {
+ *         data Ieee1609Dot2Data OPTIONAL,
+ *         extDataHash HashedData OPTIONAL,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class SignedDataPayload
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SignerIdentifier.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignerIdentifier.java
new file mode 100644
index 0000000..fc59812
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SignerIdentifier.java
@@ -0,0 +1,29 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SignerIdentifier ::= CHOICE {
+ *         digest HashedId8,
+ *         certificate SequenceOfCertificate,
+ *         self NULL,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class SignerIdentifier
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SspRange.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SspRange.java
new file mode 100644
index 0000000..74c13ce
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SspRange.java
@@ -0,0 +1,140 @@
+package org.bouncycastle.its.asn1;
+
+import java.io.IOException;
+
+import org.bouncycastle.asn1.ASN1Null;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERNull;
+
+/**
+ * <pre>
+ * SspRange ::= CHOICE {
+ *     opaque SequenceOfOctetString,
+ *     all NULL,
+ *     ...
+ *     bitmapSspRange BitmapSspRange
+ * }
+ * </pre>
+ */
+public class SspRange
+    extends ASN1Object
+{
+    private final boolean isAll;
+    private final SequenceOfOctetString opaque;
+    private final BitmapSspRange bitmapSspRange;
+
+    private SspRange()
+    {
+        isAll = true;
+        opaque = null;
+        bitmapSspRange = null;
+    }
+
+    private SspRange(SequenceOfOctetString seq)
+    {
+        this.isAll = false;
+        if (seq.size() != 2)
+        {
+            opaque = seq;
+            bitmapSspRange = null;
+        }
+        else
+        {
+            // ambiguous
+            opaque = SequenceOfOctetString.getInstance(seq);
+
+            BitmapSspRange bitMapRange;
+            try
+            {
+                bitMapRange = BitmapSspRange.getInstance(seq);
+            }
+            catch (IllegalArgumentException e)
+            {
+                bitMapRange = null;
+            }
+
+            bitmapSspRange = bitMapRange;
+        }
+    }
+
+    public SspRange(BitmapSspRange range)
+    {
+        this.isAll = false;
+        this.bitmapSspRange = range;
+        this.opaque = null;
+    }
+
+    public static SspRange getInstance(Object src)
+    {
+        if (src == null)
+        {
+            return null;
+        }
+
+        if (src instanceof SspRange)
+        {
+            return (SspRange)src;
+        }
+
+        if (src instanceof ASN1Null)
+        {
+            return new SspRange();
+        }
+
+        if (src instanceof ASN1Sequence)
+        {
+            return new SspRange(SequenceOfOctetString.getInstance(src));
+        }
+
+        if (src instanceof byte[])
+        {
+            try
+            {
+                return getInstance(ASN1Primitive.fromByteArray((byte[])src));
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("unable to parse encoded general name");
+            }
+        }
+
+        throw new IllegalArgumentException("unknown object in getInstance: " + src.getClass().getName());
+    }
+
+    public boolean isAll()
+    {
+        return isAll;
+    }
+
+    public boolean maybeOpaque()
+    {
+        return opaque != null;
+    }
+
+    public BitmapSspRange getBitmapSspRange()
+    {
+        return bitmapSspRange;
+    }
+
+    public SequenceOfOctetString getOpaque()
+    {
+        return opaque;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        if (isAll)
+        {
+            return DERNull.INSTANCE;
+        }
+
+        if (bitmapSspRange != null)
+        {
+            return bitmapSspRange.toASN1Primitive();
+        }
+
+        return opaque.toASN1Primitive();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SubjectPermissions.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SubjectPermissions.java
new file mode 100644
index 0000000..d525612
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SubjectPermissions.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     SubjectPermissions ::= CHOICE {
+ *         explicit SequenceOfPsidSspRange,
+ *         all NULL,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class SubjectPermissions
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public static SubjectPermissions getInstance(Object src)
+    {
+        if (src instanceof SubjectPermissions)
+        {
+            return (SubjectPermissions)src;
+        }
+        else if (src != null)
+        {
+            // TODO: ....
+            return null;
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmAlgorithm.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmAlgorithm.java
new file mode 100644
index 0000000..782610e
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmAlgorithm.java
@@ -0,0 +1,53 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+public class SymmAlgorithm
+    extends ASN1Object
+{
+    public static SymmAlgorithm aes128Ccm = new SymmAlgorithm(new ASN1Enumerated(0));
+    private ASN1Enumerated symmAlgorithm;
+
+    private SymmAlgorithm(ASN1Enumerated symmAlgorithm)
+    {
+        this.symmAlgorithm = symmAlgorithm;
+    }
+
+    public SymmAlgorithm(int ordinal)
+    {
+        this.symmAlgorithm = new ASN1Enumerated(ordinal);
+    }
+
+    public SymmAlgorithm getInstance(Object src)
+    {
+        if (src == null)
+        {
+            return null;
+        }
+        else if (src instanceof SymmAlgorithm)
+        {
+            return (SymmAlgorithm)src;
+        }
+        else
+        {
+            return new SymmAlgorithm(ASN1Enumerated.getInstance(src));
+        }
+    }
+
+    public ASN1Enumerated getSymmAlgorithm()
+    {
+        return symmAlgorithm;
+    }
+
+    public void setSymmAlgorithm(ASN1Enumerated symmAlgorithm)
+    {
+        this.symmAlgorithm = symmAlgorithm;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return symmAlgorithm.toASN1Primitive();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmRecipientInfo.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmRecipientInfo.java
new file mode 100644
index 0000000..db5e478
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/SymmRecipientInfo.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     SymmRecipientInfo ::= SEQUENCE {
+ *         recipientId HashedId8,
+ *         encKey SymmetricCiphertext
+ *     }
+ * </pre>
+ */
+public class SymmRecipientInfo
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedCertificate.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedCertificate.java
new file mode 100644
index 0000000..8f7c4ab
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedCertificate.java
@@ -0,0 +1,54 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+
+/**
+ * <pre>
+ *     ToBeSignedCertificate ::= SEQUENCE {
+ *         id CertificateId,
+ *         cracaId HashedId3,
+ *         crlSeries CrlSeries,
+ *         validityPeriod ValidityPeriod,
+ *         region GeographicRegion OPTIONAL,
+ *         assuranceLevel SubjectAssurance OPTIONAL,
+ *         appPermissions SequenceOfPsidSep OPTIONAL,
+ *         certIssuePermissions SequenceOfPsidGroupPermissions OPTIONAL,
+ *         certRequestPermissions NULL OPTIONAL,
+ *         encryptionKey PublicEncryptionKey OPTIONAL,
+ *         verifyKeyIndicator VerificationKeyIndicator,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class ToBeSignedCertificate
+    extends ASN1Object
+{
+//    private final CertificateId certificateId;
+
+    private ToBeSignedCertificate(ASN1Sequence seq)
+    {
+  //TODO:      this.certificateId = CertificateId.
+    }
+
+    public static ToBeSignedCertificate getInstance(Object src)
+    {
+        if (src instanceof ToBeSignedCertificate)
+        {
+            return (ToBeSignedCertificate)src;
+        }
+        else if (src != null)
+        {
+            // TODO: need choice processing here
+            return new ToBeSignedCertificate(ASN1Sequence.getInstance(src));
+        }
+
+        return null;
+    }
+
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedData.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedData.java
new file mode 100644
index 0000000..ee17894
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ToBeSignedData.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     ToBeSignedData ::= SEQUENCE {
+ *         payload SignedDataPayload,
+ *         headerInfo HeaderInfo
+ *     }
+ * </pre>
+ */
+public class ToBeSignedData
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/TwoDLocation.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/TwoDLocation.java
new file mode 100644
index 0000000..07fa3af
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/TwoDLocation.java
@@ -0,0 +1,21 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     TwoDLocation ::= SEQUENCE {
+ *         latitude Latitude,
+ *         longitude Longitude
+ *     }
+ * </pre>
+ */
+public class TwoDLocation
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/Utils.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/Utils.java
new file mode 100644
index 0000000..94aacc0
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/Utils.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.util.Arrays;
+
+class Utils
+{
+    /**
+     * <pre>
+     *     OCTET STRING (SIZE(n))
+     * </pre>
+     */
+    static byte[] octetStringFixed(byte[] octets, int n)
+    {
+        if (octets.length != n)
+        {
+            throw new IllegalArgumentException("octet string out of range");
+        }
+
+        return octets;
+    }
+
+    /**
+     * <pre>
+     *     OCTET STRING (SIZE(1..32))
+     * </pre>
+     */
+    static byte[] octetStringFixed(byte[] octets)
+    {
+        if (octets.length < 1 || octets.length > 32)
+        {
+            throw new IllegalArgumentException("octet string out of range");
+        }
+
+        return Arrays.clone(octets);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/ValidityPeriod.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/ValidityPeriod.java
new file mode 100644
index 0000000..3401f4d
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/ValidityPeriod.java
@@ -0,0 +1,25 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.DERSequence;
+
+/**
+ * <pre>
+ *     ValidityPeriod ::= SEQUENCE {
+ *         start Time32,
+ *         duration Duration
+ *     }
+ * </pre>
+ */
+public class ValidityPeriod
+    extends ASN1Object
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        return new DERSequence(v);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/its/asn1/VerificationKeyIndicator.java b/bcprov/src/main/java/org/bouncycastle/its/asn1/VerificationKeyIndicator.java
new file mode 100644
index 0000000..140c6a0
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/its/asn1/VerificationKeyIndicator.java
@@ -0,0 +1,24 @@
+package org.bouncycastle.its.asn1;
+
+import org.bouncycastle.asn1.ASN1Choice;
+import org.bouncycastle.asn1.ASN1Object;
+import org.bouncycastle.asn1.ASN1Primitive;
+
+/**
+ * <pre>
+ *     VerificationKeyIndicator ::= CHOICE {
+ *         verificationKey PublicVerificationKey,
+ *         reconstructionValue EccP256CurvePoint,
+ *         ...
+ *     }
+ * </pre>
+ */
+public class VerificationKeyIndicator
+    extends ASN1Object
+    implements ASN1Choice
+{
+    public ASN1Primitive toASN1Primitive()
+    {
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePrivateKey.java
new file mode 100644
index 0000000..e30881f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePrivateKey.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.jcajce;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+/**
+ * A composite private key class.
+ */
+public class CompositePrivateKey
+    implements PrivateKey
+{
+    private final List<PrivateKey> keys;
+
+    /**
+     * Create a composite key containing a single private key.
+     *
+     * @param keys the private keys the composite private key wraps.
+     */
+    public CompositePrivateKey(PrivateKey... keys)
+    {
+        if (keys == null || keys.length == 0)
+        {
+            throw new IllegalArgumentException("at least one public key must be provided");
+        }
+
+        List<PrivateKey> keyList = new ArrayList<PrivateKey>(keys.length);
+        for (int i = 0; i != keys.length; i++)
+        {
+            keyList.add(keys[i]);
+        }
+        this.keys = Collections.unmodifiableList(keyList);
+    }
+
+    /**
+     * Return a list of the component private keys making up this composite.
+     * 
+     * @return an immutable list of private keys.
+     */
+    public List<PrivateKey> getPrivateKeys()
+    {
+        return keys;
+    }
+
+    public String getAlgorithm()
+    {
+        return "Composite";
+    }
+
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    public byte[] getEncoded()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != keys.size(); i++)
+        {
+            v.add(PrivateKeyInfo.getInstance(keys.get(i).getEncoded()));
+        }
+
+        try
+        {
+            return new PrivateKeyInfo(
+                new AlgorithmIdentifier(MiscObjectIdentifiers.id_alg_composite), new DERSequence(v)).getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode composite key: " + e.getMessage());
+        }
+    }
+
+    public int hashCode()
+    {
+        return keys.hashCode();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (o instanceof CompositePrivateKey)
+        {
+            return keys.equals(((CompositePrivateKey)o).keys);
+        }
+
+        return false;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePublicKey.java
new file mode 100644
index 0000000..6a1ed2c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/CompositePublicKey.java
@@ -0,0 +1,103 @@
+package org.bouncycastle.jcajce;
+
+import java.io.IOException;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+
+/**
+ * A composite key class.
+ */
+public class CompositePublicKey
+    implements PublicKey
+{
+    private final List<PublicKey> keys;
+
+    /**
+     * Create a composite key containing a single public key.
+     *
+     * @param keys the public keys the composite key wraps.
+     */
+    public CompositePublicKey(PublicKey... keys)
+    {
+        if (keys == null || keys.length == 0)
+        {
+            throw new IllegalArgumentException("at least one public key must be provided");
+        }
+
+        List<PublicKey> keyList = new ArrayList<PublicKey>(keys.length);
+        for (int i = 0; i != keys.length; i++)
+        {
+            keyList.add(keys[i]);
+        }
+        this.keys = Collections.unmodifiableList(keyList);
+    }
+
+    /**
+     * Return a list of the component private keys making up this composite.
+     *
+     * @return an immutable list of private keys.
+     */
+    public List<PublicKey> getPublicKeys()
+    {
+        return keys;
+    }
+
+    public String getAlgorithm()
+    {
+        return "Composite";
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    public byte[] getEncoded()
+    {
+        ASN1EncodableVector v = new ASN1EncodableVector();
+
+        for (int i = 0; i != keys.size(); i++)
+        {
+            v.add(SubjectPublicKeyInfo.getInstance(keys.get(i).getEncoded()));
+        }
+
+        try
+        {
+            return new SubjectPublicKeyInfo(
+                new AlgorithmIdentifier(MiscObjectIdentifiers.id_alg_composite), new DERSequence(v)).getEncoded(ASN1Encoding.DER);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode composite key: " + e.getMessage());
+        }
+    }
+
+    public int hashCode()
+    {
+        return keys.hashCode();
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (o instanceof CompositePublicKey)
+        {
+            return keys.equals(((CompositePublicKey)o).keys);
+        }
+
+        return false;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationChecker.java b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationChecker.java
new file mode 100644
index 0000000..8414a9d
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationChecker.java
@@ -0,0 +1,15 @@
+package org.bouncycastle.jcajce;
+
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+
+public interface PKIXCertRevocationChecker
+{
+    void setParameter(String name, Object value);
+
+    void initialize(PKIXCertRevocationCheckerParameters params)
+        throws CertPathValidatorException;
+
+    void check(Certificate cert)
+        throws CertPathValidatorException;
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationCheckerParameters.java b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationCheckerParameters.java
new file mode 100644
index 0000000..be71c12
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertRevocationCheckerParameters.java
@@ -0,0 +1,56 @@
+package org.bouncycastle.jcajce;
+
+import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+public class PKIXCertRevocationCheckerParameters
+{
+    private final PKIXExtendedParameters paramsPKIX;
+    private final Date validDate;
+    private final CertPath certPath;
+    private final int index;
+    private final X509Certificate signingCert;
+    private final PublicKey workingPublicKey;
+
+    public PKIXCertRevocationCheckerParameters(PKIXExtendedParameters paramsPKIX, Date validDate, CertPath certPath, int index, X509Certificate signingCert, PublicKey workingPublicKey)
+    {
+        this.paramsPKIX = paramsPKIX;
+        this.validDate = validDate;
+        this.certPath = certPath;
+        this.index = index;
+        this.signingCert = signingCert;
+        this.workingPublicKey = workingPublicKey;
+    }
+
+    public PKIXExtendedParameters getParamsPKIX()
+    {
+        return paramsPKIX;
+    }
+
+    public Date getValidDate()
+    {
+        return new Date(validDate.getTime());
+    }
+
+    public CertPath getCertPath()
+    {
+        return certPath;
+    }
+
+    public int getIndex()
+    {
+        return index;
+    }
+
+    public X509Certificate getSigningCert()
+    {
+        return signingCert;
+    }
+
+    public PublicKey getWorkingPublicKey()
+    {
+        return workingPublicKey;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertStoreSelector.java b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertStoreSelector.java
index faf25d1..acad41c 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertStoreSelector.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXCertStoreSelector.java
@@ -53,6 +53,21 @@
         this.baseSelector = baseSelector;
     }
 
+    /**
+     * Return the specific certificate this selector is designed to match.
+     *
+     * @return a specific certificate where the selector has been configured explicitly.
+     */
+    public Certificate getCertificate()
+    {
+         if (baseSelector instanceof X509CertSelector)
+         {
+             return ((X509CertSelector)baseSelector).getCertificate();
+         }
+
+         return null;
+    }
+
     public boolean match(Certificate cert)
     {
         return baseSelector.match(cert);
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXExtendedParameters.java b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXExtendedParameters.java
index 34ac2b0..e0316f0 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXExtendedParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/PKIXExtendedParameters.java
@@ -22,26 +22,22 @@
     implements CertPathParameters
 {
     /**
-     * This is the default PKIX validity model. Actually there are two variants
-     * of this: The PKIX model and the modified PKIX model. The PKIX model
-     * verifies that all involved certificates must have been valid at the
-     * current time. The modified PKIX model verifies that all involved
-     * certificates were valid at the signing time. Both are indirectly choosen
-     * with the {@link PKIXParameters#setDate(Date)} method, so this
-     * methods sets the Date when <em>all</em> certificates must have been
-     * valid.
+     * This is the default PKIX validity model. Actually there are two variants of this: The PKIX
+     * model and the modified PKIX model. The PKIX model verifies that all involved certificates
+     * must have been valid at the current time. The modified PKIX model verifies that all involved
+     * certificates were valid at the signing time. Both are indirectly chosen with the
+     * {@link PKIXParameters#setDate(Date)} method, so this methods sets the Date when <em>all</em>
+     * certificates must have been valid.
      */
     public static final int PKIX_VALIDITY_MODEL = 0;
 
     /**
-     * This model uses the following validity model. Each certificate must have
-     * been valid at the moment where is was used. That means the end
-     * certificate must have been valid at the time the signature was done. The
-     * CA certificate which signed the end certificate must have been valid,
-     * when the end certificate was signed. The CA (or Root CA) certificate must
-     * have been valid, when the CA certificate was signed and so on. So the
-     * {@link PKIXParameters#setDate(Date)} method sets the time, when
-     * the <em>end certificate</em> must have been valid. It is used e.g.
+     * This model uses the following validity model. Each certificate must have been valid at the
+     * moment when it was used. That means the end certificate must have been valid at the time the
+     * signature was done. The CA certificate which signed the end certificate must have been valid,
+     * when the end certificate was signed. The CA (or Root CA) certificate must have been valid
+     * when the CA certificate was signed, and so on. So the {@link PKIXParameters#setDate(Date)}
+     * method sets the time, when the <em>end certificate</em> must have been valid. It is used e.g.
      * in the German signature law.
      */
     public static final int CHAIN_VALIDITY_MODEL = 1;
@@ -52,6 +48,7 @@
     public static class Builder
     {
         private final PKIXParameters baseParameters;
+        private final Date validityDate;
         private final Date date;
 
         private PKIXCertStoreSelector targetConstraints;
@@ -72,8 +69,8 @@
             {
                 this.targetConstraints = new PKIXCertStoreSelector.Builder(constraints).build();
             }
-            Date checkDate = baseParameters.getDate();
-            this.date = (checkDate == null) ? new Date() : checkDate;
+            this.validityDate = baseParameters.getDate();
+            this.date = (validityDate == null) ? new Date() : validityDate;
             this.revocationEnabled = baseParameters.isRevocationEnabled();
             this.trustAnchors = baseParameters.getTrustAnchors();
         }
@@ -81,6 +78,7 @@
         public Builder(PKIXExtendedParameters baseParameters)
         {
             this.baseParameters = baseParameters.baseParameters;
+            this.validityDate = baseParameters.validityDate;
             this.date = baseParameters.date;
             this.targetConstraints = baseParameters.targetConstraints;
             this.extraCertStores = new ArrayList<PKIXCertStore>(baseParameters.extraCertStores);
@@ -196,6 +194,7 @@
 
     private final PKIXParameters baseParameters;
     private final PKIXCertStoreSelector targetConstraints;
+    private final Date validityDate;
     private final Date date;
     private final List<PKIXCertStore> extraCertStores;
     private final Map<GeneralName, PKIXCertStore> namedCertificateStoreMap;
@@ -209,6 +208,7 @@
     private PKIXExtendedParameters(Builder builder)
     {
         this.baseParameters = builder.baseParameters;
+        this.validityDate = builder.validityDate;
         this.date = builder.date;
         this.extraCertStores = Collections.unmodifiableList(builder.extraCertStores);
         this.namedCertificateStoreMap = Collections.unmodifiableMap(new HashMap<GeneralName, PKIXCertStore>(builder.namedCertificateStoreMap));
@@ -242,14 +242,25 @@
         return namedCRLStoreMap;
     }
 
+    /**
+     * Returns the time at which to check the validity of the certification path. If {@code null},
+     * the current time is used.
+     *
+     * @return the {@code Date}, or {@code null} if not set
+     */
+    public Date getValidityDate()
+    {
+        return null == validityDate ? null : new Date(validityDate.getTime());
+    }
+
+    /**
+     * @deprecated Use 'getValidityDate' instead (which can return null).
+     */
     public Date getDate()
     {
         return new Date(date.getTime());
     }
 
-
-
-
     /**
      * Defaults to <code>false</code>.
      *
@@ -260,8 +271,6 @@
         return useDeltas;
     }
 
-
-
     /**
      * @return Returns the validity model.
      * @see #CHAIN_VALIDITY_MODEL
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/BCX509Certificate.java b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/BCX509Certificate.java
index 39dd1a3..5cb63d6 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/BCX509Certificate.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/BCX509Certificate.java
@@ -3,9 +3,29 @@
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.TBSCertificate;
 
+/**
+ * Interface exposing some additional methods on a BC native certificate object.
+ */
 public interface BCX509Certificate
 {
+    /**
+     * Return the certificate issuer as an X500Name.
+     *
+     * @return the issuer.
+     */
     X500Name getIssuerX500Name();
+
+    /**
+     * Return the ASN.1 class representing the TBSCertificate for this certificate.
+     *
+     * @return the issuer.
+     */
     TBSCertificate getTBSCertificateNative();
+
+    /**
+     * Return the certificate subject as an X500Name.
+     *
+     * @return the issuer.
+     */
     X500Name getSubjectX500Name();
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPrivateKey.java
new file mode 100644
index 0000000..85da567
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPrivateKey.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.jcajce.interfaces;
+
+import java.security.PrivateKey;
+
+public interface EdDSAPrivateKey
+    extends EdDSAKey, PrivateKey
+{
+    /**
+     * Return the public key associated with this private key.
+     *
+     * @return an EdDSAPublicKey
+     */
+    EdDSAPublicKey getPublicKey();
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPublicKey.java
new file mode 100644
index 0000000..8bd619c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/EdDSAPublicKey.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.jcajce.interfaces;
+
+import java.security.PublicKey;
+
+public interface EdDSAPublicKey
+    extends EdDSAKey, PublicKey
+{
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPrivateKey.java
new file mode 100644
index 0000000..7d5c600
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPrivateKey.java
@@ -0,0 +1,14 @@
+package org.bouncycastle.jcajce.interfaces;
+
+import java.security.PrivateKey;
+
+public interface XDHPrivateKey
+    extends XDHKey, PrivateKey
+{
+    /**
+     * Return the public key associated with this private key.
+     *
+     * @return an XDHPublicKey
+     */
+    XDHPublicKey getPublicKey();
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPublicKey.java
new file mode 100644
index 0000000..cdd62f8
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/interfaces/XDHPublicKey.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.jcajce.interfaces;
+
+import java.security.PublicKey;
+
+public interface XDHPublicKey
+    extends XDHKey, PublicKey
+{
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/COMPOSITE.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/COMPOSITE.java
new file mode 100644
index 0000000..36fe721
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/COMPOSITE.java
@@ -0,0 +1,136 @@
+package org.bouncycastle.jcajce.provider.asymmetric;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.CompositePrivateKey;
+import org.bouncycastle.jcajce.CompositePublicKey;
+import org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class COMPOSITE
+{
+    private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE";
+
+    private static final Map<String, String> compositeAttributes = new HashMap<String, String>();
+
+    static
+    {
+        compositeAttributes.put("SupportedKeyClasses", "org.bouncycastle.jcajce.CompositePublicKey|org.bouncycastle.jcajce.CompositePrivateKey");
+        compositeAttributes.put("SupportedKeyFormats", "PKCS#8|X.509");
+    }
+
+    private static AsymmetricKeyInfoConverter baseConverter;
+
+    public static class KeyFactory
+        extends BaseKeyFactorySpi
+    {
+        protected Key engineTranslateKey(Key key)
+            throws InvalidKeyException
+        {
+            try
+            {
+                if (key instanceof PrivateKey)
+                {
+                    return generatePrivate(PrivateKeyInfo.getInstance(key.getEncoded()));
+                }
+                else if (key instanceof PublicKey)
+                {
+                    return generatePublic(SubjectPublicKeyInfo.getInstance(key.getEncoded()));
+                }
+            }
+            catch (IOException e)
+            {
+                throw new InvalidKeyException("key could not be parsed: " + e.getMessage());
+            }
+
+            throw new InvalidKeyException("key not recognized");
+        }
+
+        public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+            throws IOException
+        {
+            return baseConverter.generatePrivate(keyInfo);
+        }
+
+        public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+            throws IOException
+        {
+            return baseConverter.generatePublic(keyInfo);
+        }
+    }
+
+    private static class CompositeKeyInfoConverter
+        implements AsymmetricKeyInfoConverter
+    {
+        private final ConfigurableProvider provider;
+
+        public CompositeKeyInfoConverter(ConfigurableProvider provider)
+        {
+            this.provider = provider;
+        }
+
+        public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+            throws IOException
+        {
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(keyInfo.getPrivateKey().getOctets());
+            PrivateKey[] privKeys = new PrivateKey[keySeq.size()];
+
+            for (int i = 0; i != keySeq.size(); i++)
+            {
+                PrivateKeyInfo privInfo = PrivateKeyInfo.getInstance(keySeq.getObjectAt(i));
+
+                privKeys[i] = provider.getKeyInfoConverter(
+                    privInfo.getPrivateKeyAlgorithm().getAlgorithm()).generatePrivate(privInfo);
+            }
+
+            return new CompositePrivateKey(privKeys);
+        }
+
+        public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+            throws IOException
+        {
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(keyInfo.getPublicKeyData().getBytes());
+            PublicKey[] pubKeys = new PublicKey[keySeq.size()];
+
+            for (int i = 0; i != keySeq.size(); i++)
+            {
+                SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(keySeq.getObjectAt(i));
+
+                pubKeys[i] = provider.getKeyInfoConverter((pubInfo.getAlgorithm().getAlgorithm())).generatePublic(pubInfo);
+            }
+
+            return new CompositePublicKey(pubKeys);
+        }
+    }
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.COMPOSITE", PREFIX + "$KeyFactory");
+            provider.addAlgorithm("KeyFactory." + MiscObjectIdentifiers.id_alg_composite, PREFIX + "$KeyFactory");
+            provider.addAlgorithm("KeyFactory.OID." + MiscObjectIdentifiers.id_alg_composite, PREFIX + "$KeyFactory");
+
+            baseConverter = new CompositeKeyInfoConverter(provider);
+
+            provider.addKeyInfoConverter(MiscObjectIdentifiers.id_alg_composite, baseConverter);
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java
index 67aef3f..b752bcb 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ECGOST.java
@@ -8,6 +8,7 @@
 
 public class ECGOST
 {
+    private static final String PREFIX_COMMON = "org.bouncycastle.jcajce.provider.asymmetric" + ".ec.";
     private static final String PREFIX = "org.bouncycastle.jcajce.provider.asymmetric" + ".ecgost.";
     private static final String PREFIX_GOST_2012 = "org.bouncycastle.jcajce.provider.asymmetric" + ".ecgost12.";
 
@@ -46,8 +47,9 @@
             provider.addAlgorithm("Alg.Alias.KeyAgreement.GOST-3410-2001", "ECGOST3410");
 
             provider.addAlgorithm("Alg.Alias.KeyAgreement." + CryptoProObjectIdentifiers.gostR3410_2001_CryptoPro_ESDH, "ECGOST3410");
-            
-            provider.addAlgorithm("AlgorithmParameters.ECGOST3410", PREFIX + "AlgorithmParametersSpi");
+
+            provider.addAlgorithm("AlgorithmParameters.ECGOST3410", PREFIX_COMMON + "AlgorithmParametersSpi");
+            provider.addAlgorithm("AlgorithmParameters.ECGOST3410-2012", PREFIX_COMMON + "AlgorithmParametersSpi");
             provider.addAlgorithm("Alg.Alias.AlgorithmParameters.GOST-3410-2001", "ECGOST3410");
 
             addSignatureAlgorithm(provider, "GOST3411",
@@ -93,7 +95,8 @@
                     "ECGOST3410-2012-256");
             provider.addAlgorithm("Alg.Alias.Signature.GOST-3410-2012-256",
                     "ECGOST3410-2012-256");
-
+            provider.addAlgorithm("Alg.Alias.Signature.GOST3411WITHECGOST3410-2012-256",
+                    "ECGOST3410-2012-256");
 
             addSignatureAlgorithm(provider, "GOST3411-2012-256", "ECGOST3410-2012-256",
                     PREFIX_GOST_2012 + "ECGOST2012SignatureSpi256",
@@ -108,7 +111,9 @@
                     "ECGOST3410-2012-512");
             provider.addAlgorithm("Alg.Alias.Signature.GOST-3410-2012-512",
                     "ECGOST3410-2012-512");
-
+            provider.addAlgorithm("Alg.Alias.Signature.GOST3411WITHECGOST3410-2012-512",
+                      "ECGOST3410-2012-512");
+            
             addSignatureAlgorithm(provider, "GOST3411-2012-512", "ECGOST3410-2012-512",
                     PREFIX_GOST_2012 + "ECGOST2012SignatureSpi512",
                     RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512);
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/EdEC.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/EdEC.java
index 60f8ea6..52da6ba 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/EdEC.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/EdEC.java
@@ -33,15 +33,15 @@
             provider.addAlgorithm("KeyFactory.X448", PREFIX + "KeyFactorySpi$X448");
             provider.addAlgorithm("KeyFactory.X25519", PREFIX + "KeyFactorySpi$X25519");
 
-            provider.addAlgorithm("KeyFactory.EDDSA", PREFIX + "KeyFactorySpi$EDDSA");
-            provider.addAlgorithm("KeyFactory.ED448", PREFIX + "KeyFactorySpi$ED448");
-            provider.addAlgorithm("KeyFactory.ED25519", PREFIX + "KeyFactorySpi$ED25519");
+            provider.addAlgorithm("KeyFactory.EDDSA", PREFIX + "KeyFactorySpi$EdDSA");
+            provider.addAlgorithm("KeyFactory.ED448", PREFIX + "KeyFactorySpi$Ed448");
+            provider.addAlgorithm("KeyFactory.ED25519", PREFIX + "KeyFactorySpi$Ed25519");
 
             provider.addAlgorithm("Signature.EDDSA", PREFIX + "SignatureSpi$EdDSA");
             provider.addAlgorithm("Signature.ED448", PREFIX + "SignatureSpi$Ed448");
             provider.addAlgorithm("Signature.ED25519", PREFIX + "SignatureSpi$Ed25519");
-            provider.addAlgorithm("Signature", EdECObjectIdentifiers.id_Ed448, PREFIX + "SignatureSpi$Ed448");
-            provider.addAlgorithm("Signature", EdECObjectIdentifiers.id_Ed25519, PREFIX + "SignatureSpi$Ed25519");
+            provider.addAlgorithm("Alg.Alias.Signature", EdECObjectIdentifiers.id_Ed448, "ED448");
+            provider.addAlgorithm("Alg.Alias.Signature", EdECObjectIdentifiers.id_Ed25519, "ED25519");
 
             provider.addAlgorithm("KeyPairGenerator.EDDSA", PREFIX + "KeyPairGeneratorSpi$EdDSA");
             provider.addAlgorithm("KeyPairGenerator.ED448", PREFIX + "KeyPairGeneratorSpi$Ed448");
@@ -72,13 +72,13 @@
             provider.addAlgorithm("KeyPairGenerator.XDH", PREFIX + "KeyPairGeneratorSpi$XDH");
             provider.addAlgorithm("KeyPairGenerator.X448", PREFIX + "KeyPairGeneratorSpi$X448");
             provider.addAlgorithm("KeyPairGenerator.X25519", PREFIX + "KeyPairGeneratorSpi$X25519");
-            provider.addAlgorithm("KeyPairGenerator", EdECObjectIdentifiers.id_X448, PREFIX + "KeyPairGeneratorSpiSpi$X448");
-            provider.addAlgorithm("KeyPairGenerator", EdECObjectIdentifiers.id_X25519, PREFIX + "KeyPairGeneratorSpiSpi$X25519");
+            provider.addAlgorithm("KeyPairGenerator", EdECObjectIdentifiers.id_X448, PREFIX + "KeyPairGeneratorSpi$X448");
+            provider.addAlgorithm("KeyPairGenerator", EdECObjectIdentifiers.id_X25519, PREFIX + "KeyPairGeneratorSpi$X25519");
 
             registerOid(provider, EdECObjectIdentifiers.id_X448, "XDH", new KeyFactorySpi.X448());
             registerOid(provider, EdECObjectIdentifiers.id_X25519, "XDH", new KeyFactorySpi.X25519());
-            registerOid(provider, EdECObjectIdentifiers.id_Ed448, "EDDSA", new KeyFactorySpi.ED448());
-            registerOid(provider, EdECObjectIdentifiers.id_Ed25519, "EDDSA", new KeyFactorySpi.ED25519());
+            registerOid(provider, EdECObjectIdentifiers.id_Ed448, "EDDSA", new KeyFactorySpi.Ed448());
+            registerOid(provider, EdECObjectIdentifiers.id_Ed25519, "EDDSA", new KeyFactorySpi.Ed25519());
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/RSA.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/RSA.java
index c32690a..8cb7727 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/RSA.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/RSA.java
@@ -81,6 +81,9 @@
             provider.addAlgorithm("KeyFactory.RSA", PREFIX + "KeyFactorySpi");
             provider.addAlgorithm("KeyPairGenerator.RSA", PREFIX + "KeyPairGeneratorSpi");
 
+            provider.addAlgorithm("KeyFactory.RSASSA-PSS", PREFIX + "KeyFactorySpi");
+            provider.addAlgorithm("KeyPairGenerator.RSASSA-PSS", PREFIX + "KeyPairGeneratorSpi$PSS");
+
             AsymmetricKeyInfoConverter keyFact = new KeyFactorySpi();
 
             registerOid(provider, PKCSObjectIdentifiers.rsaEncryption, "RSA", keyFact);
@@ -260,6 +263,9 @@
             provider.addAlgorithm("Alg.Alias.Signature." + digest + "WithRSA/PSS", digest + "WITHRSAANDMGF1");
             provider.addAlgorithm("Alg.Alias.Signature." + digest + "withRSAandMGF1", digest + "WITHRSAANDMGF1");
             provider.addAlgorithm("Alg.Alias.Signature." + digest + "WithRSAAndMGF1", digest + "WITHRSAANDMGF1");
+            provider.addAlgorithm("Alg.Alias.Signature." + digest + "withRSASSA-PSS", digest + "WITHRSAANDMGF1");
+            provider.addAlgorithm("Alg.Alias.Signature." + digest + "WithRSASSA-PSS", digest + "WITHRSAANDMGF1");
+            provider.addAlgorithm("Alg.Alias.Signature." + digest + "WITHRSASSA-PSS", digest + "WITHRSAANDMGF1");
             provider.addAlgorithm("Signature." + digest + "WITHRSAANDMGF1", className);
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java
index b1ef7e5..8fb8da7 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/AlgorithmParameterGeneratorSpi.java
@@ -52,14 +52,7 @@
 
         int certainty = PrimeCertaintyCalculator.getDefaultCertainty(strength);
 
-        if (random != null)
-        {
-            pGen.init(strength, certainty, random);
-        }
-        else
-        {
-            pGen.init(strength, certainty, CryptoServicesRegistrar.getSecureRandom());
-        }
+        pGen.init(strength, certainty, CryptoServicesRegistrar.getSecureRandom(random));
 
         DHParameters p = pGen.generateParameters();
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java
index 21182ef..f8b4bf5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPrivateKey.java
@@ -27,6 +27,7 @@
 import org.bouncycastle.crypto.params.DHValidationParameters;
 import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl;
 import org.bouncycastle.jcajce.spec.DHDomainParameterSpec;
+import org.bouncycastle.jcajce.spec.DHExtendedPrivateKeySpec;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
 
 
@@ -58,7 +59,14 @@
         DHPrivateKeySpec spec)
     {
         this.x = spec.getX();
-        this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
+        if (spec instanceof DHExtendedPrivateKeySpec)
+        {
+            this.dhSpec = ((DHExtendedPrivateKeySpec)spec).getParams();
+        }
+        else
+        {
+            this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
+        }
     }
 
     public BCDHPrivateKey(
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java
index 039b8d3..e9d3d1a 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dh/BCDHPublicKey.java
@@ -24,6 +24,7 @@
 import org.bouncycastle.crypto.params.DHValidationParameters;
 import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
 import org.bouncycastle.jcajce.spec.DHDomainParameterSpec;
+import org.bouncycastle.jcajce.spec.DHExtendedPublicKeySpec;
 
 public class BCDHPublicKey
     implements DHPublicKey
@@ -40,8 +41,25 @@
         DHPublicKeySpec spec)
     {
         this.y = spec.getY();
-        this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
-        this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(spec.getP(), spec.getG()));
+        if (spec instanceof DHExtendedPublicKeySpec)
+        {
+            this.dhSpec = ((DHExtendedPublicKeySpec)spec).getParams();
+        }
+        else
+        {
+            this.dhSpec = new DHParameterSpec(spec.getP(), spec.getG());
+
+        }
+
+        if (dhSpec instanceof DHDomainParameterSpec)
+        {
+            DHDomainParameterSpec dhSp = (DHDomainParameterSpec)dhSpec;
+            this.dhPublicKey = new DHPublicKeyParameters(y, dhSp.getDomainParameters());
+        }
+        else
+        {
+            this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(spec.getP(), spec.getG()));
+        }
     }
 
     BCDHPublicKey(
@@ -49,7 +67,15 @@
     {
         this.y = key.getY();
         this.dhSpec = key.getParams();
-        this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(dhSpec.getP(), dhSpec.getG()));
+        if (dhSpec instanceof DHDomainParameterSpec)
+        {
+            DHDomainParameterSpec dhSp = (DHDomainParameterSpec)dhSpec;
+            this.dhPublicKey = new DHPublicKeyParameters(y, dhSp.getDomainParameters());
+        }
+        else
+        {
+            this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(dhSpec.getP(), dhSpec.getG()));
+        }
     }
 
     BCDHPublicKey(
@@ -105,12 +131,14 @@
             if (params.getL() != null)
             {
                 this.dhSpec = new DHParameterSpec(params.getP(), params.getG(), params.getL().intValue());
+                this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(dhSpec.getP(), dhSpec.getG(), null, dhSpec.getL()));
             }
             else
             {
                 this.dhSpec = new DHParameterSpec(params.getP(), params.getG());
+                this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(dhSpec.getP(), dhSpec.getG()));
             }
-            this.dhPublicKey = new DHPublicKeyParameters(y, new DHParameters(dhSpec.getP(), dhSpec.getG()));
+
         }
         else if (id.equals(X9ObjectIdentifiers.dhpublicnumber))
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java
index 601fddd..0aa9691 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/BCDSAPublicKey.java
@@ -51,7 +51,7 @@
         DSAPublicKeyParameters params)
     {
         this.y = params.getY();
-        if (params != null)
+        if (params.getParameters() != null)
         {
             this.dsaSpec = new DSAParameterSpec(params.getParameters().getP(), params.getParameters().getQ(), params.getParameters().getG());
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
index d0e3674..3eb8006 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dsa/DSASigner.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.jcajce.provider.asymmetric.dsa;
 
 import java.math.BigInteger;
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -129,6 +130,11 @@
         return signer.verifySignature(hash, sig[0], sig[1]);
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     protected void engineSetParameter(
         AlgorithmParameterSpec params)
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java
index 52a2da4..ce5a7c7 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/dstu/SignatureSpi.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.jcajce.provider.asymmetric.dstu;
 
 import java.math.BigInteger;
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -173,6 +174,11 @@
         throw new UnsupportedOperationException("engineSetParameter unsupported");
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     /**
      * @deprecated replaced with #engineSetParameter(java.security.spec.AlgorithmParameterSpec)
      */
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
index f54d723..cad65c4 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/BCECPublicKey.java
@@ -28,6 +28,7 @@
 import org.bouncycastle.jce.interfaces.ECPointEncoder;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Properties;
 
 public class BCECPublicKey
     implements ECPublicKey, org.bouncycastle.jce.interfaces.ECPublicKey, ECPointEncoder
@@ -234,11 +235,13 @@
 
     public byte[] getEncoded()
     {
+        boolean compress = withCompression || Properties.isOverrideSet("org.bouncycastle.ec.enable_pc");
+
         AlgorithmIdentifier algId = new AlgorithmIdentifier(
             X9ObjectIdentifiers.id_ecPublicKey,
-            ECUtils.getDomainParametersFromName(ecSpec, withCompression));
+            ECUtils.getDomainParametersFromName(ecSpec, compress));
 
-        byte[] pubKeyOctets = ecPublicKey.getQ().getEncoded(withCompression);
+        byte[] pubKeyOctets = ecPublicKey.getQ().getEncoded(compress);
 
         // stored curve is null if ImplicitlyCa
         return KeyUtil.getEncodedSubjectPublicKeyInfo(algId, pubKeyOctets);
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
index f6f8b1c..7b6b228 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/KeyFactorySpi.java
@@ -66,7 +66,7 @@
         Class spec)
         throws InvalidKeySpecException
     {
-        if (spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class) && key instanceof ECPublicKey)
+        if ((spec.isAssignableFrom(KeySpec.class) || spec.isAssignableFrom(java.security.spec.ECPublicKeySpec.class)) && key instanceof ECPublicKey)
         {
             ECPublicKey k = (ECPublicKey)key;
             if (k.getParams() != null)
@@ -80,7 +80,7 @@
                 return new java.security.spec.ECPublicKeySpec(k.getW(), EC5Util.convertSpec(EC5Util.convertCurve(implicitSpec.getCurve(), implicitSpec.getSeed()), implicitSpec));
             }
         }
-        else if (spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class) && key instanceof ECPrivateKey)
+        else if ((spec.isAssignableFrom(KeySpec.class) || spec.isAssignableFrom(java.security.spec.ECPrivateKeySpec.class)) && key instanceof ECPrivateKey)
         {
             ECPrivateKey k = (ECPrivateKey)key;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
index 73d2ae5..3013905 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ec/SignatureSpi.java
@@ -1,5 +1,6 @@
 package org.bouncycastle.jcajce.provider.asymmetric.ec;
 
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -55,6 +56,11 @@
         }
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+    
     static public class ecDSA
         extends SignatureSpi
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
index 2cf4ff8..8d247b5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/BCECGOST3410PrivateKey.java
@@ -241,20 +241,12 @@
                 ASN1ObjectIdentifier oid = ASN1ObjectIdentifier.getInstance(params.getParameters());
                 X9ECParameters ecP = ECUtil.getNamedCurveByOid(oid);
 
-                String curveName;
-                if (ecP == null) // GOST Curve
+                if (ecP == null)
                 {
-                    ECDomainParameters gParam = ECGOST3410NamedCurves.getByOID(oid);
-
-                    // Point compression ignored for these temporary parameters
-                    ecP = new X9ECParameters(gParam.getCurve(), new X9ECPoint(gParam.getG(), false), gParam.getN(), gParam.getH(), gParam.getSeed());
-
-                    curveName = ECGOST3410NamedCurves.getName(oid);
+                    throw new IllegalStateException();
                 }
-                else
-                {
-                    curveName = ECUtil.getCurveName(oid);
-                }
+
+                String curveName = ECUtil.getCurveName(oid);
 
                 EllipticCurve ellipticCurve = EC5Util.convertCurve(ecP.getCurve(), ecP.getSeed());
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
index 0b6d6c6..80ef273 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/KeyPairGeneratorSpi.java
@@ -8,7 +8,9 @@
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.ECGenParameterSpec;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -139,24 +141,21 @@
     private void init(GOST3410ParameterSpec gostParams, SecureRandom random)
         throws InvalidAlgorithmParameterException
     {
-        ECDomainParameters ecP = ECGOST3410NamedCurves.getByOID(gostParams.getPublicKeyParamSet());
+        ASN1ObjectIdentifier oid = gostParams.getPublicKeyParamSet();
+
+        X9ECParameters ecP = ECGOST3410NamedCurves.getByOIDX9(oid);
         if (ecP == null)
         {
-            throw new InvalidAlgorithmParameterException("unknown curve: " + gostParams.getPublicKeyParamSet());
+            throw new InvalidAlgorithmParameterException("unknown curve: " + oid);
         }
 
-        this.ecParams = new ECNamedCurveSpec(
-            ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()),
-            ecP.getCurve(),
-            ecP.getG(),
-            ecP.getN(),
-            ecP.getH(),
-            ecP.getSeed());
-        
+        this.ecParams = new ECNamedCurveSpec(ECGOST3410NamedCurves.getName(oid), ecP.getCurve(), ecP.getG(), ecP.getN(),
+            ecP.getH(), ecP.getSeed());
+
         param = new ECKeyGenerationParameters(
             new ECGOST3410Parameters(
-                new ECNamedDomainParameters(gostParams.getPublicKeyParamSet(), ecP),
-                gostParams.getPublicKeyParamSet(), gostParams.getDigestParamSet(), gostParams.getEncryptionParamSet()), random);
+                new ECNamedDomainParameters(oid, ecP),
+                oid, gostParams.getDigestParamSet(), gostParams.getEncryptionParamSet()), random);
 
         engine.init(param);
         initialised = true;
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
index 3bda40f..2ee7f8a 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost/SignatureSpi.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.jcajce.provider.asymmetric.ecgost;
 
 import java.math.BigInteger;
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -210,6 +211,11 @@
         throw new UnsupportedOperationException("engineSetParameter unsupported");
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     static AsymmetricKeyParameter generatePublicKeyParameter(
             PublicKey key)
     throws InvalidKeyException
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/BCECGOST3410_2012PrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/BCECGOST3410_2012PrivateKey.java
index c77697d..541ff3e 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/BCECGOST3410_2012PrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/BCECGOST3410_2012PrivateKey.java
@@ -41,6 +41,7 @@
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
 import org.bouncycastle.math.ec.ECCurve;
+import org.bouncycastle.util.Arrays;
 
 /**
  * Represent two kind of GOST34.10 2012 PrivateKeys: with 256 and 512 size
@@ -214,23 +215,26 @@
                 EC5Util.convertPoint(spec.getG()),
                 spec.getN(), spec.getH());
 
-            ASN1Encodable privKey = info.parsePrivateKey();
+            ASN1OctetString privEnc = info.getPrivateKey();
 
-            if (privKey instanceof ASN1Integer)
+            if (privEnc.getOctets().length == 32 || privEnc.getOctets().length == 64)
             {
-                this.d = ASN1Integer.getInstance(privKey).getPositiveValue();
+                this.d = new BigInteger(1, Arrays.reverse(privEnc.getOctets()));
             }
             else
             {
-                byte[] encVal = ASN1OctetString.getInstance(privKey).getOctets();
-                byte[] dVal = new byte[encVal.length];
+                ASN1Encodable privKey = info.parsePrivateKey();
 
-                for (int i = 0; i != encVal.length; i++)
+                if (privKey instanceof ASN1Integer)
                 {
-                    dVal[i] = encVal[encVal.length - 1 - i];
+                    this.d = ASN1Integer.getInstance(privKey).getPositiveValue();
                 }
+                else
+                {
+                    byte[] encVal = ASN1OctetString.getInstance(privKey).getOctets();
 
-                this.d = new BigInteger(1, dVal);
+                    this.d = new BigInteger(1, Arrays.reverse(encVal));
+                }
             }
         }
         else
@@ -245,7 +249,7 @@
 
                 if (ecP == null) // GOST Curve
                 {
-                    ECDomainParameters gParam = ECGOST3410NamedCurves.getByOID(oid);
+                    X9ECParameters gParam = ECGOST3410NamedCurves.getByOIDX9(oid);
                     EllipticCurve ellipticCurve = EC5Util.convertCurve(gParam.getCurve(), gParam.getSeed());
 
                     ecSpec = new ECNamedCurveSpec(
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi256.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi256.java
index bfa73e9..3d5eee1 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi256.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi256.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.jcajce.provider.asymmetric.ecgost12;
 
 import java.math.BigInteger;
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -200,6 +201,11 @@
         throw new UnsupportedOperationException("engineSetParameter unsupported");
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     /**
      * @deprecated replaced with "#engineSetParameter(java.security.spec.AlgorithmParameterSpec)"
      */
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi512.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi512.java
index 4ea3e92..d090498 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi512.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/ECGOST2012SignatureSpi512.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.jcajce.provider.asymmetric.ecgost12;
 
 import java.math.BigInteger;
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -202,6 +203,11 @@
         throw new UnsupportedOperationException("engineSetParameter unsupported");
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     /**
      * @deprecated replaced with #engineSetParameter(java.security.spec.AlgorithmParameterSpec)
      */
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/KeyPairGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/KeyPairGeneratorSpi.java
index 0059a20..4742303 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/KeyPairGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/ecgost12/KeyPairGeneratorSpi.java
@@ -9,6 +9,7 @@
 import java.security.spec.ECGenParameterSpec;
 
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
 import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -142,7 +143,7 @@
     private void init(GOST3410ParameterSpec gostParams, SecureRandom random)
         throws InvalidAlgorithmParameterException
     {
-        ECDomainParameters ecP = ECGOST3410NamedCurves.getByOID(gostParams.getPublicKeyParamSet());
+        X9ECParameters ecP = ECGOST3410NamedCurves.getByOIDX9(gostParams.getPublicKeyParamSet());
         if (ecP == null)
         {
             throw new InvalidAlgorithmParameterException("unknown curve: " + gostParams.getPublicKeyParamSet());
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPrivateKey.java
index 92061da..5bdb41e 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPrivateKey.java
@@ -14,15 +14,17 @@
 import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
 import org.bouncycastle.crypto.params.Ed448PrivateKeyParameters;
 import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
-import org.bouncycastle.jcajce.interfaces.EdDSAKey;
+import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
+import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Properties;
 
 public class BCEdDSAPrivateKey
-    implements EdDSAKey, PrivateKey
+    implements EdDSAPrivateKey
 {
     static final long serialVersionUID = 1L;
     
-    private transient AsymmetricKeyParameter eddsaPrivateKey;
+    transient AsymmetricKeyParameter eddsaPrivateKey;
 
     private final boolean hasPublicKey;
     private final byte[] attributes;
@@ -74,7 +76,7 @@
             ASN1Set attrSet = ASN1Set.getInstance(attributes);
             PrivateKeyInfo privInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(eddsaPrivateKey, attrSet);
 
-            if (hasPublicKey)
+            if (hasPublicKey && !Properties.isOverrideSet("org.bouncycastle.pkcs8.v1_info_only"))
             {
                 return privInfo.getEncoded();
             }
@@ -89,6 +91,18 @@
         }
     }
 
+    public EdDSAPublicKey getPublicKey()
+    {
+        if (eddsaPrivateKey instanceof Ed448PrivateKeyParameters)
+        {
+            return new BCEdDSAPublicKey(((Ed448PrivateKeyParameters)eddsaPrivateKey).generatePublicKey());
+        }
+        else
+        {
+            return new BCEdDSAPublicKey(((Ed25519PrivateKeyParameters)eddsaPrivateKey).generatePublicKey());
+        }
+    }
+
     AsymmetricKeyParameter engineGetKeyParameters()
     {
         return eddsaPrivateKey;
@@ -115,12 +129,12 @@
             return true;
         }
 
-        if (!(o instanceof BCEdDSAPrivateKey))
+        if (!(o instanceof PrivateKey))
         {
             return false;
         }
 
-        BCEdDSAPrivateKey other = (BCEdDSAPrivateKey)o;
+        PrivateKey other = (PrivateKey)o;
 
         return Arrays.areEqual(other.getEncoded(), this.getEncoded());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPublicKey.java
index c6b6d72..3133c0d 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCEdDSAPublicKey.java
@@ -11,15 +11,15 @@
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
 import org.bouncycastle.crypto.params.Ed448PublicKeyParameters;
-import org.bouncycastle.jcajce.interfaces.EdDSAKey;
+import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
 import org.bouncycastle.util.Arrays;
 
 public class BCEdDSAPublicKey
-    implements EdDSAKey, PublicKey
+    implements EdDSAPublicKey
 {
     static final long serialVersionUID = 1L;
 
-    private transient AsymmetricKeyParameter eddsaPublicKey;
+    transient AsymmetricKeyParameter eddsaPublicKey;
 
     BCEdDSAPublicKey(AsymmetricKeyParameter pubKey)
     {
@@ -120,12 +120,12 @@
             return true;
         }
 
-        if (!(o instanceof BCEdDSAPublicKey))
+        if (!(o instanceof PublicKey))
         {
             return false;
         }
 
-        BCEdDSAPublicKey other = (BCEdDSAPublicKey)o;
+        PublicKey other = (PublicKey)o;
 
         return Arrays.areEqual(other.getEncoded(), this.getEncoded());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPrivateKey.java
index 3485158..9a8d0c5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPrivateKey.java
@@ -5,7 +5,6 @@
 import java.io.ObjectOutputStream;
 import java.security.PrivateKey;
 
-import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
@@ -14,15 +13,17 @@
 import org.bouncycastle.crypto.params.X25519PrivateKeyParameters;
 import org.bouncycastle.crypto.params.X448PrivateKeyParameters;
 import org.bouncycastle.crypto.util.PrivateKeyInfoFactory;
-import org.bouncycastle.jcajce.interfaces.XDHKey;
+import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
+import org.bouncycastle.jcajce.interfaces.XDHPublicKey;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Properties;
 
 public class BCXDHPrivateKey
-    implements XDHKey, PrivateKey
+    implements XDHPrivateKey
 {
     static final long serialVersionUID = 1L;
 
-    private transient AsymmetricKeyParameter xdhPrivateKey;
+    transient AsymmetricKeyParameter xdhPrivateKey;
 
     private final boolean hasPublicKey;
     private final byte[] attributes;
@@ -46,7 +47,14 @@
     private void populateFromPrivateKeyInfo(PrivateKeyInfo keyInfo)
         throws IOException
     {
-        ASN1Encodable keyOcts = keyInfo.parsePrivateKey();
+        ASN1OctetString keyOcts = keyInfo.getPrivateKey();
+        byte[] infoOcts = keyOcts.getOctets();
+
+        if (infoOcts.length != 32 && infoOcts.length != 56) // exact length of X25519/X448 secret used in Java 11
+        {
+            keyOcts = ASN1OctetString.getInstance(keyInfo.parsePrivateKey());
+        }
+
         if (EdECObjectIdentifiers.id_X448.equals(keyInfo.getPrivateKeyAlgorithm().getAlgorithm()))
         {
             xdhPrivateKey = new X448PrivateKeyParameters(ASN1OctetString.getInstance(keyOcts).getOctets(), 0);
@@ -74,7 +82,7 @@
             ASN1Set attrSet = ASN1Set.getInstance(attributes);
             PrivateKeyInfo privInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(xdhPrivateKey, attrSet);
 
-            if (hasPublicKey)
+            if (hasPublicKey && !Properties.isOverrideSet("org.bouncycastle.pkcs8.v1_info_only"))
             {
                 return privInfo.getEncoded();
             }
@@ -89,6 +97,18 @@
         }
     }
 
+    public XDHPublicKey getPublicKey()
+    {
+        if (xdhPrivateKey instanceof X448PrivateKeyParameters)
+        {
+            return new BCXDHPublicKey(((X448PrivateKeyParameters)xdhPrivateKey).generatePublicKey());
+        }
+        else
+        {
+            return new BCXDHPublicKey(((X25519PrivateKeyParameters)xdhPrivateKey).generatePublicKey());
+        }
+    }
+
     AsymmetricKeyParameter engineGetKeyParameters()
     {
         return xdhPrivateKey;
@@ -115,12 +135,12 @@
             return true;
         }
 
-        if (!(o instanceof BCXDHPrivateKey))
+        if (!(o instanceof PrivateKey))
         {
             return false;
         }
 
-        BCXDHPrivateKey other = (BCXDHPrivateKey)o;
+        PrivateKey other = (PrivateKey)o;
 
         return Arrays.areEqual(other.getEncoded(), this.getEncoded());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPublicKey.java
index 143bf11..69b4056 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/BCXDHPublicKey.java
@@ -11,15 +11,15 @@
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
 import org.bouncycastle.crypto.params.X448PublicKeyParameters;
-import org.bouncycastle.jcajce.interfaces.XDHKey;
+import org.bouncycastle.jcajce.interfaces.XDHPublicKey;
 import org.bouncycastle.util.Arrays;
 
 public class BCXDHPublicKey
-    implements XDHKey, PublicKey
+    implements XDHPublicKey
 {
     static final long serialVersionUID = 1L;
 
-    private transient AsymmetricKeyParameter xdhPublicKey;
+    transient AsymmetricKeyParameter xdhPublicKey;
 
     BCXDHPublicKey(AsymmetricKeyParameter pubKey)
     {
@@ -120,12 +120,12 @@
             return true;
         }
 
-        if (!(o instanceof BCXDHPublicKey))
+        if (!(o instanceof PublicKey))
         {
             return false;
         }
 
-        BCXDHPublicKey other = (BCXDHPublicKey)o;
+        PublicKey other = (PublicKey)o;
 
         return Arrays.areEqual(other.getEncoded(), this.getEncoded());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyFactorySpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyFactorySpi.java
index a5e73a9..1e86214 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyFactorySpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyFactorySpi.java
@@ -9,6 +9,7 @@
 import java.security.spec.KeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
@@ -16,6 +17,7 @@
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
@@ -161,8 +163,26 @@
         {
             byte[] enc = ((X509EncodedKeySpec)keySpec).getEncoded();
             // optimise if we can
-            if (specificBase == 0 || specificBase == enc[8])
+            if ((specificBase == 0 || specificBase == enc[8]))
             {
+                // watch out for badly placed DER NULL - the default X509Cert will add these!
+                if (enc[9] == 0x05 && enc[10] == 0x00)
+                {
+                    SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(enc);
+
+                    keyInfo = new SubjectPublicKeyInfo(
+                        new AlgorithmIdentifier(keyInfo.getAlgorithm().getAlgorithm()), keyInfo.getPublicKeyData().getBytes());
+
+                    try
+                    {
+                        enc = keyInfo.getEncoded(ASN1Encoding.DER);
+                    }
+                    catch (IOException e)
+                    {
+                        throw new InvalidKeySpecException("attempt to reconstruct key failed: " + e.getMessage());
+                    }
+                }
+
                 switch (enc[8])
                 {
                 case x448_type:
@@ -281,28 +301,28 @@
         }
     }
 
-    public static class EDDSA
+    public static class EdDSA
         extends KeyFactorySpi
     {
-        public EDDSA()
+        public EdDSA()
         {
             super("EdDSA", false, 0);
         }
     }
 
-    public static class ED448
+    public static class Ed448
         extends KeyFactorySpi
     {
-        public ED448()
+        public Ed448()
         {
             super("Ed448", false, Ed448_type);
         }
     }
 
-    public static class ED25519
+    public static class Ed25519
         extends KeyFactorySpi
     {
-        public ED25519()
+        public Ed25519()
         {
             super("Ed25519", false, Ed25519_type);
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyPairGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyPairGeneratorSpi.java
index cf07d68..7726ad8 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyPairGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/KeyPairGeneratorSpi.java
@@ -49,42 +49,56 @@
     public void initialize(int strength, SecureRandom secureRandom)
     {
         this.secureRandom = secureRandom;
-
-        switch (strength)
+        try
         {
-        case 255:
-        case 256:
-            switch (algorithm)
+            switch (strength)
             {
-            case EdDSA:
-            case Ed25519:
-                setupGenerator(Ed25519);
+            case 255:
+            case 256:
+                switch (algorithm)
+                {
+                case EdDSA:
+                case Ed25519:
+                    algorithmCheck(Ed25519);
+                    this.generator = new Ed25519KeyPairGenerator();
+                    setupGenerator(Ed25519);
+                    break;
+                case XDH:
+                case X25519:
+                    algorithmCheck(X25519);
+                    this.generator = new X25519KeyPairGenerator();
+                    setupGenerator(X25519);
+                    break;
+                default:
+                    throw new InvalidParameterException("key size not configurable");
+                }
                 break;
-            case XDH:
-            case X25519:
-                setupGenerator(X25519);
+            case 448:
+                switch (algorithm)
+                {
+                case EdDSA:
+                case Ed448:
+                    algorithmCheck(Ed448);
+                    this.generator = new Ed448KeyPairGenerator();
+                    setupGenerator(Ed448);
+                    break;
+                case XDH:
+                case X448:
+                    algorithmCheck(X448);
+                    this.generator = new X448KeyPairGenerator();
+                    setupGenerator(X448);
+                    break;
+                default:
+                    throw new InvalidParameterException("key size not configurable");
+                }
                 break;
             default:
-                throw new InvalidParameterException("key size not configurable");
+                throw new InvalidParameterException("unknown key size");
             }
-            break;
-        case 448:
-            switch (algorithm)
-            {
-            case EdDSA:
-            case Ed448:
-                setupGenerator(Ed448);
-                break;
-            case XDH:
-            case X448:
-                setupGenerator(X448);
-                break;
-            default:
-                throw new InvalidParameterException("key size not configurable");
-            }
-            break;
-        default:
-            throw new InvalidParameterException("unknown key size");
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            throw new InvalidParameterException(e.getMessage());
         }
     }
 
@@ -145,7 +159,6 @@
             {
                 throw new InvalidAlgorithmParameterException("parameterSpec for wrong curve type");
             }
-            this.algorithm = algorithm;
         }
     }
 
@@ -194,28 +207,20 @@
 
         switch (algorithm)
         {
-        case Ed448:
-            return new KeyPair(new BCEdDSAPublicKey(kp.getPublic()), new BCEdDSAPrivateKey(kp.getPrivate()));
-        case Ed25519:
-            return new KeyPair(new BCEdDSAPublicKey(kp.getPublic()), new BCEdDSAPrivateKey(kp.getPrivate()));
+        case XDH:
+        case X25519:
         case X448:
             return new KeyPair(new BCXDHPublicKey(kp.getPublic()), new BCXDHPrivateKey(kp.getPrivate()));
-        case X25519:
-            return new KeyPair(new BCXDHPublicKey(kp.getPublic()), new BCXDHPrivateKey(kp.getPrivate()));
-        }
 
-        throw new IllegalStateException("generator not correctly initialized");
+        default:
+            return new KeyPair(new BCEdDSAPublicKey(kp.getPublic()), new BCEdDSAPrivateKey(kp.getPrivate()));
+        }
     }
 
     private void setupGenerator(int algorithm)
     {
         initialised = true;
 
-        if (secureRandom == null)
-        {
-            secureRandom = new SecureRandom();
-        }
-
         switch (algorithm)
         {
         case Ed448:
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/SignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/SignatureSpi.java
index 85ec7e0..3d9d58f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/SignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/edec/SignatureSpi.java
@@ -1,5 +1,6 @@
 package org.bouncycastle.jcajce.provider.asymmetric.edec;
 
+import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.InvalidParameterException;
 import java.security.PrivateKey;
@@ -137,6 +138,11 @@
         throw new UnsupportedOperationException("engineGetParameter unsupported");
     }
 
+    protected AlgorithmParameters engineGetParameters()
+    {
+        return null;
+    }
+
     public final static class EdDSA
         extends SignatureSpi
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java
index e706e68..ab67017 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/gost/AlgorithmParameterGeneratorSpi.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.jce.spec.GOST3410ParameterSpec;
 import org.bouncycastle.jce.spec.GOST3410PublicKeyParameterSetSpec;
 
-public abstract class AlgorithmParameterGeneratorSpi
+public class AlgorithmParameterGeneratorSpi
     extends BaseAlgorithmParameterGeneratorSpi
 {
     protected SecureRandom random;
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java
index b6b769b..477c3f3 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateCrtKey.java
@@ -7,8 +7,6 @@
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.spec.RSAPrivateCrtKeySpec;
 
-import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -51,6 +49,20 @@
         this.crtCoefficient = key.getQInv();
     }
 
+    BCRSAPrivateCrtKey(
+        AlgorithmIdentifier algorithmIdentifier,
+        RSAPrivateCrtKeyParameters key)
+    {
+        super(algorithmIdentifier, key);
+        
+        this.publicExponent = key.getPublicExponent();
+        this.primeP = key.getP();
+        this.primeQ = key.getQ();
+        this.primeExponentP = key.getDP();
+        this.primeExponentQ = key.getDQ();
+        this.crtCoefficient = key.getQInv();
+    }
+
     /**
      * construct a private key from an RSAPrivateCrtKeySpec
      *
@@ -102,7 +114,7 @@
         PrivateKeyInfo info)
         throws IOException
     {
-        this(RSAPrivateKey.getInstance(info.parsePrivateKey()));
+        this(info.getPrivateKeyAlgorithm(), RSAPrivateKey.getInstance(info.parsePrivateKey()));
     }
 
     /**
@@ -111,7 +123,14 @@
     BCRSAPrivateCrtKey(
         RSAPrivateKey key)
     {
-        super(new RSAPrivateCrtKeyParameters(key.getModulus(),
+        this(BCRSAPublicKey.DEFAULT_ALGORITHM_IDENTIFIER, key);
+    }
+
+    BCRSAPrivateCrtKey(
+        AlgorithmIdentifier algorithmIdentifier,
+        RSAPrivateKey key)
+    {
+        super(algorithmIdentifier, new RSAPrivateCrtKeyParameters(key.getModulus(),
                                 key.getPublicExponent(), key.getPrivateExponent(),
                                 key.getPrime1(), key.getPrime2(), key.getExponent1(), key.getExponent2(), key.getCoefficient()));
 
@@ -143,7 +162,7 @@
      */
     public byte[] getEncoded()
     {
-        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPrivateKey(getModulus(), getPublicExponent(), getPrivateExponent(), getPrimeP(), getPrimeQ(), getPrimeExponentP(), getPrimeExponentQ(), getCrtCoefficient()));
+        return KeyUtil.getEncodedPrivateKeyInfo(algorithmIdentifier, new RSAPrivateKey(getModulus(), getPublicExponent(), getPrivateExponent(), getPrimeP(), getPrimeQ(), getPrimeExponentP(), getPrimeExponentQ(), getCrtCoefficient()));
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java
index cf9843e..f92a430 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPrivateKey.java
@@ -10,7 +10,6 @@
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.params.RSAKeyParameters;
@@ -28,7 +27,9 @@
 
     protected BigInteger modulus;
     protected BigInteger privateExponent;
+    private byte[]       algorithmIdentifierEnc = getEncoding(BCRSAPublicKey.DEFAULT_ALGORITHM_IDENTIFIER);
 
+    protected transient AlgorithmIdentifier algorithmIdentifier = BCRSAPublicKey.DEFAULT_ALGORITHM_IDENTIFIER;
     protected transient RSAKeyParameters rsaPrivateKey;
     protected transient PKCS12BagAttributeCarrierImpl   attrCarrier = new PKCS12BagAttributeCarrierImpl();
 
@@ -41,6 +42,18 @@
     }
 
     BCRSAPrivateKey(
+        AlgorithmIdentifier algID,
+        RSAKeyParameters key)
+    {
+        this.algorithmIdentifier = algID;
+        this.algorithmIdentifierEnc = getEncoding(algID);
+        
+        this.modulus = key.getModulus();
+        this.privateExponent = key.getExponent();
+        this.rsaPrivateKey = key;
+    }
+
+    BCRSAPrivateKey(
         RSAPrivateKeySpec spec)
     {
         this.modulus = spec.getModulus();
@@ -56,8 +69,11 @@
         this.rsaPrivateKey = new RSAKeyParameters(true, modulus, privateExponent);
     }
 
-    BCRSAPrivateKey(org.bouncycastle.asn1.pkcs.RSAPrivateKey key)
+    BCRSAPrivateKey(AlgorithmIdentifier algID, org.bouncycastle.asn1.pkcs.RSAPrivateKey key)
     {
+        this.algorithmIdentifier = algID;
+        this.algorithmIdentifierEnc = getEncoding(algID);
+
         this.modulus = key.getModulus();
         this.privateExponent = key.getPrivateExponent();
         this.rsaPrivateKey = new RSAKeyParameters(true, modulus, privateExponent);
@@ -75,6 +91,10 @@
 
     public String getAlgorithm()
     {
+        if (algorithmIdentifier.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            return "RSASSA-PSS";
+        }
         return "RSA";
     }
 
@@ -90,7 +110,7 @@
 
     public byte[] getEncoded()
     {
-        return KeyUtil.getEncodedPrivateKeyInfo(new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new org.bouncycastle.asn1.pkcs.RSAPrivateKey(getModulus(), ZERO, getPrivateExponent(), ZERO, ZERO, ZERO, ZERO, ZERO));
+        return KeyUtil.getEncodedPrivateKeyInfo(algorithmIdentifier, new org.bouncycastle.asn1.pkcs.RSAPrivateKey(getModulus(), ZERO, getPrivateExponent(), ZERO, ZERO, ZERO, ZERO, ZERO));
     }
 
     public boolean equals(Object o)
@@ -140,6 +160,13 @@
     {
         in.defaultReadObject();
 
+        if (algorithmIdentifierEnc == null)
+        {
+            algorithmIdentifierEnc = getEncoding(BCRSAPublicKey.DEFAULT_ALGORITHM_IDENTIFIER);
+        }
+
+        this.algorithmIdentifier = AlgorithmIdentifier.getInstance(algorithmIdentifierEnc);
+
         this.attrCarrier = new PKCS12BagAttributeCarrierImpl();
         this.rsaPrivateKey = new RSAKeyParameters(true, modulus, privateExponent);
     }
@@ -162,4 +189,16 @@
 
         return buf.toString();
     }
+
+    private static byte[] getEncoding(AlgorithmIdentifier algorithmIdentifier)
+    {
+        try
+        {
+            return algorithmIdentifier.getEncoded();
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java
index 1dcf353..2d7e11b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/BCRSAPublicKey.java
@@ -18,7 +18,7 @@
 public class BCRSAPublicKey
     implements RSAPublicKey
 {
-    private static final AlgorithmIdentifier DEFAULT_ALGORITHM_IDENTIFIER = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
+    static final AlgorithmIdentifier DEFAULT_ALGORITHM_IDENTIFIER = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
 
     static final long serialVersionUID = 2675817738516720772L;
 
@@ -31,7 +31,14 @@
     BCRSAPublicKey(
         RSAKeyParameters key)
     {
-        this.algorithmIdentifier = DEFAULT_ALGORITHM_IDENTIFIER;
+        this(DEFAULT_ALGORITHM_IDENTIFIER, key);
+    }
+
+    BCRSAPublicKey(
+        AlgorithmIdentifier algId,
+        RSAKeyParameters key)
+    {
+        this.algorithmIdentifier = algId;
         this.modulus = key.getModulus();
         this.publicExponent = key.getExponent();
         this.rsaPublicKey = key;
@@ -100,6 +107,10 @@
 
     public String getAlgorithm()
     {
+        if (algorithmIdentifier.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+        {
+            return "RSASSA-PSS";
+        }
         return "RSA";
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java
index 7a74224..e36140c 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyFactorySpi.java
@@ -40,19 +40,13 @@
         Class spec)
         throws InvalidKeySpecException
     {
-        if (spec.isAssignableFrom(RSAPublicKeySpec.class) && key instanceof RSAPublicKey)
+        if ((spec.isAssignableFrom(KeySpec.class) || spec.isAssignableFrom(RSAPublicKeySpec.class)) && key instanceof RSAPublicKey)
         {
             RSAPublicKey k = (RSAPublicKey)key;
 
             return new RSAPublicKeySpec(k.getModulus(), k.getPublicExponent());
         }
-        else if (spec.isAssignableFrom(RSAPrivateKeySpec.class) && key instanceof java.security.interfaces.RSAPrivateKey)
-        {
-            java.security.interfaces.RSAPrivateKey k = (java.security.interfaces.RSAPrivateKey)key;
-
-            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
-        }
-        else if (spec.isAssignableFrom(RSAPrivateCrtKeySpec.class) && key instanceof RSAPrivateCrtKey)
+        else if ((spec.isAssignableFrom(KeySpec.class) || spec.isAssignableFrom(RSAPrivateCrtKeySpec.class)) && key instanceof RSAPrivateCrtKey)
         {
             RSAPrivateCrtKey k = (RSAPrivateCrtKey)key;
 
@@ -63,6 +57,12 @@
                 k.getPrimeExponentP(), k.getPrimeExponentQ(),
                 k.getCrtCoefficient());
         }
+        else if ((spec.isAssignableFrom(KeySpec.class) || spec.isAssignableFrom(RSAPrivateKeySpec.class)) && key instanceof java.security.interfaces.RSAPrivateKey)
+        {
+            java.security.interfaces.RSAPrivateKey k = (java.security.interfaces.RSAPrivateKey)key;
+
+            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
+        }
         else if (spec.isAssignableFrom(OpenSSHPublicKeySpec.class) && key instanceof RSAPublicKey)
         {
             try
@@ -247,7 +247,7 @@
 
             if (rsaPrivKey.getCoefficient().intValue() == 0)
             {
-                return new BCRSAPrivateKey(rsaPrivKey);
+                return new BCRSAPrivateKey(keyInfo.getPrivateKeyAlgorithm(), rsaPrivKey);
             }
             else
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java
index 4334815..3bb13f6 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/KeyPairGeneratorSpi.java
@@ -7,6 +7,9 @@
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
@@ -18,27 +21,33 @@
 public class KeyPairGeneratorSpi
     extends java.security.KeyPairGenerator
 {
-    public KeyPairGeneratorSpi(
-        String algorithmName)
-    {
-        super(algorithmName);
-    }
+    private static final AlgorithmIdentifier PKCS_ALGID = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE);
+    private static final AlgorithmIdentifier PSS_ALGID = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_RSASSA_PSS);
 
     final static BigInteger defaultPublicExponent = BigInteger.valueOf(0x10001);
 
     RSAKeyGenerationParameters param;
     RSAKeyPairGenerator engine;
+    AlgorithmIdentifier algId;
 
-    public KeyPairGeneratorSpi()
+    public KeyPairGeneratorSpi(
+        String algorithmName,
+        AlgorithmIdentifier algId)
     {
-        super("RSA");
+        super(algorithmName);
 
+        this.algId = algId;
         engine = new RSAKeyPairGenerator();
         param = new RSAKeyGenerationParameters(defaultPublicExponent,
             CryptoServicesRegistrar.getSecureRandom(), 2048, PrimeCertaintyCalculator.getDefaultCertainty(2048));
         engine.init(param);
     }
 
+    public KeyPairGeneratorSpi()
+    {
+        this("RSA", PKCS_ALGID);
+    }
+
     public void initialize(
         int strength,
         SecureRandom random)
@@ -73,7 +82,16 @@
         RSAKeyParameters pub = (RSAKeyParameters)pair.getPublic();
         RSAPrivateCrtKeyParameters priv = (RSAPrivateCrtKeyParameters)pair.getPrivate();
 
-        return new KeyPair(new BCRSAPublicKey(pub),
-            new BCRSAPrivateCrtKey(priv));
+        return new KeyPair(new BCRSAPublicKey(algId, pub),
+            new BCRSAPrivateCrtKey(algId, priv));
+    }
+
+    public static class PSS
+        extends KeyPairGeneratorSpi
+    {
+        public PSS()
+        {
+            super("RSASSA-PSS", PSS_ALGID);
+        }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
index f8872f8..dea1d48 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/rsa/PSSSignatureSpi.java
@@ -42,6 +42,7 @@
     private byte trailer;
     private boolean isRaw;
     private RSAKeyParameters key;
+    private SecureRandom random;
 
     private org.bouncycastle.crypto.signers.PSSSigner pss;
     private boolean isInitState = true;
@@ -123,15 +124,8 @@
         SecureRandom random)
         throws InvalidKeyException
     {
-        if (!(privateKey instanceof RSAPrivateKey))
-        {
-            throw new InvalidKeyException("Supplied key is not a RSAPrivateKey instance");
-        }
-
-        key = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
-        pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
-        pss.init(true, new ParametersWithRandom(key, random));
-        isInitState = true;
+        this.random = random;
+        engineInitSign(privateKey);
     }
 
     protected void engineInitSign(
@@ -145,7 +139,16 @@
 
         key = RSAUtil.generatePrivateKeyParameter((RSAPrivateKey)privateKey);
         pss = new org.bouncycastle.crypto.signers.PSSSigner(signer, contentDigest, mgfDigest, saltLength, trailer);
-        pss.init(true, key);
+
+        if (random != null)
+        {
+            pss.init(true, new ParametersWithRandom(key, random));
+        }
+        else
+        {
+            pss.init(true, key);
+        }
+
         isInitState = true;
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
index e2f3378..26e97b8 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/EC5Util.java
@@ -13,12 +13,16 @@
 import java.util.Set;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
+import org.bouncycastle.asn1.cryptopro.GOST3410PublicKeyAlgParameters;
 import org.bouncycastle.asn1.x9.ECNamedCurveTable;
 import org.bouncycastle.asn1.x9.X962Parameters;
 import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.crypto.ec.CustomNamedCurves;
 import org.bouncycastle.crypto.params.ECDomainParameters;
 import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
+import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
@@ -90,15 +94,28 @@
         {
             curve = configuration.getEcImplicitlyCa().getCurve();
         }
-        else if (acceptableCurves.isEmpty())
-        {
-            X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
-
-            curve = ecP.getCurve();
-        }
         else
         {
-            throw new IllegalStateException("encoded parameters not acceptable");
+            ASN1Sequence pSeq = ASN1Sequence.getInstance(params.getParameters());
+            if (acceptableCurves.isEmpty())
+            {
+                if (pSeq.size() > 3)
+                {
+                    X9ECParameters ecP = X9ECParameters.getInstance(pSeq);
+
+                    curve = ecP.getCurve();
+                }
+                else    // GOST parameters
+                {
+                    ASN1ObjectIdentifier gostCurve = ASN1ObjectIdentifier.getInstance(pSeq.getObjectAt(0));
+
+                    curve = ECGOST3410NamedCurves.getByOIDX9(gostCurve).getCurve();
+                }
+            }
+            else
+            {
+                throw new IllegalStateException("encoded parameters not acceptable");
+            }
         }
 
         return curve;
@@ -158,25 +175,45 @@
         }
         else
         {
-            X9ECParameters ecP = X9ECParameters.getInstance(params.getParameters());
-
-            ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
-
-            if (ecP.getH() != null)
+            ASN1Sequence pSeq = ASN1Sequence.getInstance(params.getParameters());
+            if (pSeq.size() > 3)
             {
-                ecSpec = new ECParameterSpec(
-                    ellipticCurve,
-                    convertPoint(ecP.getG()),
-                    ecP.getN(),
-                    ecP.getH().intValue());
+                X9ECParameters ecP = X9ECParameters.getInstance(pSeq);
+
+                ellipticCurve = EC5Util.convertCurve(curve, ecP.getSeed());
+
+                if (ecP.getH() != null)
+                {
+                    ecSpec = new ECParameterSpec(
+                        ellipticCurve,
+                        convertPoint(ecP.getG()),
+                        ecP.getN(),
+                        ecP.getH().intValue());
+                }
+                else
+                {
+                    ecSpec = new ECParameterSpec(
+                        ellipticCurve,
+                        convertPoint(ecP.getG()),
+                        ecP.getN(),
+                        1);      // TODO: not strictly correct... need to fix the test data...
+                }
             }
-            else
+            else    // GOST parameters
             {
-                ecSpec = new ECParameterSpec(
+                GOST3410PublicKeyAlgParameters gostParams = GOST3410PublicKeyAlgParameters.getInstance(pSeq);
+
+                ECNamedCurveParameterSpec spec = ECGOST3410NamedCurveTable.getParameterSpec(ECGOST3410NamedCurves.getName(
+                    gostParams.getPublicKeyParamSet()));
+
+                curve = spec.getCurve();
+                ellipticCurve = EC5Util.convertCurve(curve, spec.getSeed());
+
+                ecSpec = new ECNamedCurveSpec(
+                    ECGOST3410NamedCurves.getName(gostParams.getPublicKeyParamSet()),
                     ellipticCurve,
-                    convertPoint(ecP.getG()),
-                    ecP.getN(),
-                    1);      // TODO: not strictly correct... need to fix the test data...
+                    EC5Util.convertPoint(spec.getG()),
+                    spec.getN(), spec.getH());
             }
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
index abd9f7e..5ab9163 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/util/ECUtil.java
@@ -155,7 +155,7 @@
 
                 ecP = (X9ECParameters)extraCurves.get(oid);
             }
-            domainParameters = new ECNamedDomainParameters(oid, ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
+            domainParameters = new ECNamedDomainParameters(oid, ecP);
         }
         else if (params.isImplicitlyCA())
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureCreator.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureCreator.java
new file mode 100644
index 0000000..63369e0
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/SignatureCreator.java
@@ -0,0 +1,11 @@
+package org.bouncycastle.jcajce.provider.asymmetric.x509;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+
+interface SignatureCreator
+{
+    Signature createSignature(String sigName)
+        throws NoSuchAlgorithmException, NoSuchProviderException;
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLImpl.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLImpl.java
index 6a7b1d2..e6c8fe9 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLImpl.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CRLImpl.java
@@ -23,18 +23,23 @@
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
+import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERBitString;
 import org.bouncycastle.asn1.util.ASN1Dump;
 import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.CRLDistPoint;
 import org.bouncycastle.asn1.x509.CRLNumber;
 import org.bouncycastle.asn1.x509.CertificateList;
@@ -43,16 +48,17 @@
 import org.bouncycastle.asn1.x509.GeneralNames;
 import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
 import org.bouncycastle.asn1.x509.TBSCertList;
+import org.bouncycastle.asn1.x509.Time;
+import org.bouncycastle.jcajce.CompositePublicKey;
 import org.bouncycastle.jcajce.io.OutputStreamFactory;
 import org.bouncycastle.jcajce.util.JcaJceHelper;
 import org.bouncycastle.jce.X509Principal;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Hex;
 
 /**
  * The following extensions are listed in RFC 2459 as relevant to CRLs
- *
+ * <p>
  * Authority Key Identifier
  * Issuer Alternative Name
  * CRL Number
@@ -169,80 +175,208 @@
         throws CRLException, NoSuchAlgorithmException,
         InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        Signature sig;
-
-        try
+        doVerify(key, new SignatureCreator()
         {
-            sig = bcHelper.createSignature(getSigAlgName());
-        }
-        catch (Exception e)
-        {
-            sig = Signature.getInstance(getSigAlgName());
-        }
-
-        doVerify(key, sig);
+            public Signature createSignature(String sigName)
+                throws NoSuchAlgorithmException, NoSuchProviderException
+            {
+                try
+                {
+                    return bcHelper.createSignature(sigName);
+                }
+                catch (Exception e)
+                {
+                    return Signature.getInstance(sigName);
+                }
+            }
+        });
     }
 
-    public void verify(PublicKey key, String sigProvider)
+    public void verify(PublicKey key, final String sigProvider)
         throws CRLException, NoSuchAlgorithmException,
         InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        Signature sig;
-
-        if (sigProvider != null)
+        doVerify(key, new SignatureCreator()
         {
-            sig = Signature.getInstance(getSigAlgName(), sigProvider);
-        }
-        else
-        {
-            sig = Signature.getInstance(getSigAlgName());
-        }
-
-        doVerify(key, sig);
+            public Signature createSignature(String sigName)
+                throws NoSuchAlgorithmException, NoSuchProviderException
+            {
+                if (sigProvider != null)
+                {
+                    return Signature.getInstance(sigName, sigProvider);
+                }
+                else
+                {
+                    return Signature.getInstance(sigName);
+                }
+            }
+        });
     }
 
-    public void verify(PublicKey key, Provider sigProvider)
+    public void verify(PublicKey key, final Provider sigProvider)
         throws CRLException, NoSuchAlgorithmException,
         InvalidKeyException, SignatureException
     {
-        Signature sig;
-
-        if (sigProvider != null)
+        try
         {
-            sig = Signature.getInstance(getSigAlgName(), sigProvider);
+            doVerify(key, new SignatureCreator()
+            {
+                public Signature createSignature(String sigName)
+                    throws NoSuchAlgorithmException, NoSuchProviderException
+                {
+                    if (sigProvider != null)
+                    {
+                        return Signature.getInstance(getSigAlgName(), sigProvider);
+                    }
+                    else
+                    {
+                        return Signature.getInstance(getSigAlgName());
+                    }
+                }
+            });
         }
-        else
+        catch (NoSuchProviderException e)
         {
-            sig = Signature.getInstance(getSigAlgName());
+            // can't happen, but just in case
+            throw new NoSuchAlgorithmException("provider issue: " + e.getMessage());
         }
-
-        doVerify(key, sig);
     }
 
-    private void doVerify(PublicKey key, Signature sig)
+    private void doVerify(PublicKey key, SignatureCreator sigCreator)
         throws CRLException, NoSuchAlgorithmException,
-        InvalidKeyException, SignatureException
+        InvalidKeyException, SignatureException, NoSuchProviderException
     {
         if (!c.getSignatureAlgorithm().equals(c.getTBSCertList().getSignature()))
         {
             throw new CRLException("Signature algorithm on CertificateList does not match TBSCertList.");
         }
 
+        if (key instanceof CompositePublicKey && X509SignatureUtil.isCompositeAlgorithm(c.getSignatureAlgorithm()))
+        {
+            List<PublicKey> pubKeys = ((CompositePublicKey)key).getPublicKeys();
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(c.getSignatureAlgorithm().getParameters());
+            ASN1Sequence sigSeq = ASN1Sequence.getInstance(DERBitString.getInstance(c.getSignature()).getBytes());
+
+            boolean success = false;
+            for (int i = 0; i != pubKeys.size(); i++)
+            {
+                if (pubKeys.get(i) == null)
+                {
+                    continue;
+                }
+
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                String sigName = X509SignatureUtil.getSignatureName(sigAlg);
+
+                Signature signature = sigCreator.createSignature(sigName);
+
+                SignatureException sigExc = null;
+
+                try
+                {
+                    checkSignature(
+                        (PublicKey)pubKeys.get(i), signature,
+                        sigAlg.getParameters(),
+                        DERBitString.getInstance(sigSeq.getObjectAt(i)).getBytes());
+                    success = true;
+                }
+                catch (SignatureException e)
+                {
+                    sigExc = e;
+                }
+
+                if (sigExc != null)
+                {
+                    throw sigExc;
+                }
+            }
+
+            if (!success)
+            {
+                throw new InvalidKeyException("no matching key found");
+            }
+        }
+        else if (X509SignatureUtil.isCompositeAlgorithm(c.getSignatureAlgorithm()))
+        {
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(c.getSignatureAlgorithm().getParameters());
+            ASN1Sequence sigSeq = ASN1Sequence.getInstance(DERBitString.getInstance(c.getSignature()).getBytes());
+
+            boolean success = false;
+            for (int i = 0; i != sigSeq.size(); i++)
+            {
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                String sigName = X509SignatureUtil.getSignatureName(sigAlg);
+
+                SignatureException sigExc = null;
+
+                try
+                {
+                    Signature signature = sigCreator.createSignature(sigName);
+
+                    checkSignature(
+                        key, signature,
+                        sigAlg.getParameters(),
+                        DERBitString.getInstance(sigSeq.getObjectAt(i)).getBytes());
+
+                    success = true;
+                }
+                catch (InvalidKeyException e)
+                {
+                    // ignore
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // ignore
+                }
+                catch (SignatureException e)
+                {
+                    sigExc = e;
+                }
+
+                if (sigExc != null)
+                {
+                    throw sigExc;
+                }
+            }
+
+            if (!success)
+            {
+                throw new InvalidKeyException("no matching key found");
+            }
+        }
+        else
+        {
+            Signature sig = sigCreator.createSignature(getSigAlgName());
+
+            if (sigAlgParams == null)
+            {
+                checkSignature(key, sig, null, this.getSignature());
+            }
+            else
+            {
+                try
+                {
+                    checkSignature(key, sig, ASN1Primitive.fromByteArray(sigAlgParams), this.getSignature());
+                }
+                catch (IOException e)
+                {
+                    throw new SignatureException("cannot decode signature parameters: " + e.getMessage());
+                }
+            }
+        }
+    }
+
+    private void checkSignature(PublicKey key, Signature sig, ASN1Encodable sigAlgParams, byte[] encSig)
+        throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CRLException
+    {
         if (sigAlgParams != null)
         {
-            try
-            {
-                // needs to be called before initVerify().
-                X509SignatureUtil.setSignatureParameters(sig, ASN1Primitive.fromByteArray(sigAlgParams));
-            }
-            catch (IOException e)
-            {
-                throw new SignatureException("cannot decode signature parameters: " + e.getMessage());
-            }
+            // needs to be called before initVerify().
+            X509SignatureUtil.setSignatureParameters(sig, sigAlgParams);
         }
 
         sig.initVerify(key);
-        
+
         try
         {
             OutputStream sigOut = new BufferedOutputStream(OutputStreamFactory.createStream(sig), 512);
@@ -256,7 +390,7 @@
             throw new CRLException(e.toString());
         }
 
-        if (!sig.verify(this.getSignature()))
+        if (!sig.verify(encSig))
         {
             throw new SignatureException("CRL does not verify with supplied public key.");
         }
@@ -291,14 +425,11 @@
 
     public Date getNextUpdate()
     {
-        if (c.getNextUpdate() != null)
-        {
-            return c.getNextUpdate().getDate();
-        }
+        Time nextUpdate = c.getNextUpdate();
 
-        return null;
+        return null == nextUpdate ? null : nextUpdate.getDate();
     }
- 
+
     private Set loadCRLEntries()
     {
         Set entrySet = new HashSet();
@@ -418,23 +549,7 @@
         buf.append("  Signature Algorithm: ").append(this.getSigAlgName())
             .append(nl);
 
-        byte[] sig = this.getSignature();
-
-        buf.append("            Signature: ").append(
-            new String(Hex.encode(sig, 0, 20))).append(nl);
-        for (int i = 20; i < sig.length; i += 20)
-        {
-            if (i < sig.length - 20)
-            {
-                buf.append("                       ").append(
-                    new String(Hex.encode(sig, i, 20))).append(nl);
-            }
-            else
-            {
-                buf.append("                       ").append(
-                    new String(Hex.encode(sig, i, sig.length - i))).append(nl);
-            }
-        }
+        X509SignatureUtil.prettyPrintSignature(this.getSignature(), buf, nl);
 
         Extensions extensions = c.getTBSCertList().getExtensions();
 
@@ -449,7 +564,7 @@
 
             while (e.hasMoreElements())
             {
-                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) e.nextElement();
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
                 Extension ext = extensions.getExtension(oid);
 
                 if (ext.getExtnValue() != null)
@@ -472,14 +587,14 @@
                             buf.append(
                                 "Base CRL: "
                                     + new CRLNumber(ASN1Integer.getInstance(
-                                        dIn.readObject()).getPositiveValue()))
+                                    dIn.readObject()).getPositiveValue()))
                                 .append(nl);
                         }
                         else if (oid
                             .equals(Extension.issuingDistributionPoint))
                         {
                             buf.append(
-                               IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
+                                IssuingDistributionPoint.getInstance(dIn.readObject())).append(nl);
                         }
                         else if (oid
                             .equals(Extension.cRLDistributionPoints))
@@ -565,7 +680,7 @@
                 {
                     X500Name issuer;
 
-                    if (cert instanceof  X509Certificate)
+                    if (cert instanceof X509Certificate)
                     {
                         issuer = X500Name.getInstance(((X509Certificate)cert).getIssuerX500Principal().getEncoded());
                     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
index f2a1d00..4907fb5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
@@ -57,6 +57,7 @@
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.jcajce.CompositePublicKey;
 import org.bouncycastle.jcajce.interfaces.BCX509Certificate;
 import org.bouncycastle.jcajce.io.OutputStreamFactory;
 import org.bouncycastle.jcajce.util.JcaJceHelper;
@@ -64,8 +65,8 @@
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Properties;
 import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Hex;
 
 abstract class X509CertificateImpl
     extends X509Certificate
@@ -75,14 +76,18 @@
     protected org.bouncycastle.asn1.x509.Certificate c;
     protected BasicConstraints basicConstraints;
     protected boolean[] keyUsage;
+    protected String sigAlgName;
+    protected byte[] sigAlgParams;
 
     X509CertificateImpl(JcaJceHelper bcHelper, org.bouncycastle.asn1.x509.Certificate c,
-        BasicConstraints basicConstraints, boolean[] keyUsage)
+        BasicConstraints basicConstraints, boolean[] keyUsage, String sigAlgName, byte[] sigAlgParams)
     {
         this.bcHelper = bcHelper;
         this.c = c;
         this.basicConstraints = basicConstraints;
         this.keyUsage = keyUsage;
+        this.sigAlgName = sigAlgName;
+        this.sigAlgParams = sigAlgParams;
     }
 
     public X500Name getIssuerX500Name()
@@ -203,7 +208,7 @@
      */
     public String getSigAlgName()
     {
-        return X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        return sigAlgName;
     }
 
     /**
@@ -219,21 +224,7 @@
      */
     public byte[] getSigAlgParams()
     {
-        if (c.getSignatureAlgorithm().getParameters() != null)
-        {
-            try
-            {
-                return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER);
-            }
-            catch (IOException e)
-            {
-                return null;
-            }
-        }
-        else
-        {
-            return null;
-        }
+        return Arrays.clone(sigAlgParams);
     }
 
     public boolean[] getIssuerUniqueID()
@@ -500,20 +491,7 @@
         buf.append("           Public Key: ").append(this.getPublicKey()).append(nl);
         buf.append("  Signature Algorithm: ").append(this.getSigAlgName()).append(nl);
 
-        byte[]  sig = this.getSignature();
-
-        buf.append("            Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl);
-        for (int i = 20; i < sig.length; i += 20)
-        {
-            if (i < sig.length - 20)
-            {
-                buf.append("                       ").append(new String(Hex.encode(sig, i, 20))).append(nl);
-            }
-            else
-            {
-                buf.append("                       ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl);
-            }
-        }
+        X509SignatureUtil.prettyPrintSignature(this.getSignature(), buf, nl);
 
         Extensions extensions = c.getTBSCertificate().getExtensions();
 
@@ -587,66 +565,214 @@
         throws CertificateException, NoSuchAlgorithmException,
         InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        Signature   signature;
-        String      sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
-        
-        try
+        doVerify(key, new SignatureCreator()
         {
-            signature = bcHelper.createSignature(sigName);
-        }
-        catch (Exception e)
-        {
-            signature = Signature.getInstance(sigName);
-        }
-        
-        checkSignature(key, signature);
+            public Signature createSignature(String sigName)
+                throws NoSuchAlgorithmException
+            {
+                try
+                {
+                    return bcHelper.createSignature(sigName);
+                }
+                catch (Exception e)
+                {
+                    return Signature.getInstance(sigName);
+                }
+            }
+        });
     }
     
     public final void verify(
         PublicKey   key,
-        String      sigProvider)
+        final String      sigProvider)
         throws CertificateException, NoSuchAlgorithmException,
         InvalidKeyException, NoSuchProviderException, SignatureException
     {
-        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
-        Signature signature;
-
-        if (sigProvider != null)
+        doVerify(key, new SignatureCreator()
         {
-            signature = Signature.getInstance(sigName, sigProvider);
-        }
-        else
-        {
-            signature = Signature.getInstance(sigName);
-        }
-        
-        checkSignature(key, signature);
+            public Signature createSignature(String sigName)
+                throws NoSuchAlgorithmException, NoSuchProviderException
+            {
+                if (sigProvider != null)
+                {
+                    return Signature.getInstance(sigName, sigProvider);
+                }
+                else
+                {
+                    return Signature.getInstance(sigName);
+                }
+            }
+        });
     }
 
     public final void verify(
         PublicKey   key,
-        Provider sigProvider)
+        final Provider sigProvider)
         throws CertificateException, NoSuchAlgorithmException,
         InvalidKeyException, SignatureException
     {
-        String    sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
-        Signature signature;
-
-        if (sigProvider != null)
+        try
         {
-            signature = Signature.getInstance(sigName, sigProvider);
+            doVerify(key, new SignatureCreator()
+            {
+                public Signature createSignature(String sigName)
+                    throws NoSuchAlgorithmException
+                {
+                    if (sigProvider != null)
+                    {
+                        return Signature.getInstance(sigName, sigProvider);
+                    }
+                    else
+                    {
+                        return Signature.getInstance(sigName);
+                    }
+                }
+            });
+        }
+        catch (NoSuchProviderException e)
+        {
+            // can't happen, but just in case
+            throw new NoSuchAlgorithmException("provider issue: " + e.getMessage());
+        }
+    }
+
+    private void doVerify(
+        PublicKey key,
+        SignatureCreator signatureCreator)
+        throws CertificateException, NoSuchAlgorithmException,
+        InvalidKeyException, SignatureException, NoSuchProviderException
+    {
+        if (key instanceof CompositePublicKey && X509SignatureUtil.isCompositeAlgorithm(c.getSignatureAlgorithm()))
+        {
+            List<PublicKey> pubKeys = ((CompositePublicKey)key).getPublicKeys();
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(c.getSignatureAlgorithm().getParameters());
+            ASN1Sequence sigSeq = ASN1Sequence.getInstance(DERBitString.getInstance(c.getSignature()).getBytes());
+
+            boolean success = false;
+            for (int i = 0; i != pubKeys.size(); i++)
+            {
+                if (pubKeys.get(i) == null)
+                {
+                    continue;
+                }
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                String sigName = X509SignatureUtil.getSignatureName(sigAlg);
+
+                Signature signature = signatureCreator.createSignature(sigName);
+
+                SignatureException sigExc = null;
+
+                try
+                {
+                    checkSignature(
+                        (PublicKey)pubKeys.get(i), signature,
+                        sigAlg.getParameters(),
+                        DERBitString.getInstance(sigSeq.getObjectAt(i)).getBytes());
+                    success = true;
+                }
+                catch (SignatureException e)
+                {
+                    sigExc = e;
+                }
+
+                if (sigExc != null)
+                {
+                    throw sigExc;
+                }
+            }
+
+            if (!success)
+            {
+                throw new InvalidKeyException("no matching key found");
+            }
+        }
+        else if (X509SignatureUtil.isCompositeAlgorithm(c.getSignatureAlgorithm()))
+        {
+            ASN1Sequence keySeq = ASN1Sequence.getInstance(c.getSignatureAlgorithm().getParameters());
+            ASN1Sequence sigSeq = ASN1Sequence.getInstance(DERBitString.getInstance(c.getSignature()).getBytes());
+
+            boolean success = false;
+            for (int i = 0; i != sigSeq.size(); i++)
+            {
+                AlgorithmIdentifier sigAlg = AlgorithmIdentifier.getInstance(keySeq.getObjectAt(i));
+                String sigName = X509SignatureUtil.getSignatureName(sigAlg);
+
+                SignatureException sigExc = null;
+
+                try
+                {
+                    Signature signature = signatureCreator.createSignature(sigName);
+
+                    checkSignature(
+                        key, signature,
+                        sigAlg.getParameters(),
+                        DERBitString.getInstance(sigSeq.getObjectAt(i)).getBytes());
+
+                    success = true;
+                }
+                catch (InvalidKeyException e)
+                {
+                    // ignore
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    // ignore
+                }
+                catch (SignatureException e)
+                {
+                    sigExc = e;
+                }
+
+                if (sigExc != null)
+                {
+                    throw sigExc;
+                }
+            }
+
+            if (!success)
+            {
+                throw new InvalidKeyException("no matching key found");
+            }
         }
         else
         {
-            signature = Signature.getInstance(sigName);
-        }
+            String sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
 
-        checkSignature(key, signature);
+            Signature signature = signatureCreator.createSignature(sigName);
+
+            if (key instanceof CompositePublicKey)
+            {
+                List<PublicKey> keys = ((CompositePublicKey)key).getPublicKeys();
+
+                for (int i = 0; i != keys.size(); i++)
+                {
+                    try
+                    {
+                        checkSignature((PublicKey)keys.get(i), signature,
+                            c.getSignatureAlgorithm().getParameters(), this.getSignature());
+                        return;     // found the match!
+                    }
+                    catch (InvalidKeyException e)
+                    {
+                        // continue;
+                    }
+                }
+
+                throw new InvalidKeyException("no matching signature found");
+            }
+            else
+            {
+                checkSignature(key, signature,
+                    c.getSignatureAlgorithm().getParameters(), this.getSignature());
+            }
+        }
     }
 
     private void checkSignature(
         PublicKey key, 
-        Signature signature) 
+        Signature signature,
+        ASN1Encodable params,
+        byte[] sigBytes)
         throws CertificateException, NoSuchAlgorithmException, 
             SignatureException, InvalidKeyException
     {
@@ -655,8 +781,6 @@
             throw new CertificateException("signature algorithm in TBS cert not same as outer cert");
         }
 
-        ASN1Encodable params = c.getSignatureAlgorithm().getParameters();
-
         // TODO This should go after the initVerify?
         X509SignatureUtil.setSignatureParameters(signature, params);
 
@@ -675,7 +799,7 @@
             throw new CertificateEncodingException(e.toString());
         }
 
-        if (!signature.verify(this.getSignature()))
+        if (!signature.verify(sigBytes))
         {
             throw new SignatureException("certificate does not verify with supplied key");
         }
@@ -688,27 +812,40 @@
             return false;
         }
 
-        if (id1.getParameters() == null)
+        if (Properties.isOverrideSet("org.bouncycastle.x509.allow_absent_equiv_NULL"))
         {
-            if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
+            if (id1.getParameters() == null)
             {
-                return false;
+                if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE))
+                {
+                    return false;
+                }
+
+                return true;
             }
 
-            return true;
-        }
-
-        if (id2.getParameters() == null)
-        {
-            if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
+            if (id2.getParameters() == null)
             {
-                return false;
-            }
+                if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE))
+                {
+                    return false;
+                }
 
-            return true;
+                return true;
+            }
         }
-        
-        return id1.getParameters().equals(id2.getParameters());
+
+        if (id1.getParameters() != null)
+        {
+            return id1.getParameters().equals(id2.getParameters());
+        }
+
+        if (id2.getParameters() != null)
+        {
+            return id2.getParameters().equals(id1.getParameters());
+        }
+
+        return true;
     }
 
     private static Collection getAlternativeNames(org.bouncycastle.asn1.x509.Certificate c, String oid)
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateInternal.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateInternal.java
index fb3044e..b6d8ada 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateInternal.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateInternal.java
@@ -10,9 +10,9 @@
     private final byte[] encoding;
 
     X509CertificateInternal(JcaJceHelper bcHelper, org.bouncycastle.asn1.x509.Certificate c,
-        BasicConstraints basicConstraints, boolean[] keyUsage, byte[] encoding)
+        BasicConstraints basicConstraints, boolean[] keyUsage, String sigAlgName, byte[] sigAlgParams, byte[] encoding)
     {
-        super(bcHelper, c, basicConstraints, keyUsage);
+        super(bcHelper, c, basicConstraints, keyUsage, sigAlgName, sigAlgParams);
 
         this.encoding = encoding;
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
index 1cd0dfa..3076776 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateObject.java
@@ -12,6 +12,7 @@
 
 import org.bouncycastle.asn1.ASN1BitString;
 import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.DERBitString;
@@ -39,7 +40,7 @@
     X509CertificateObject(JcaJceHelper bcHelper, org.bouncycastle.asn1.x509.Certificate c)
         throws CertificateParsingException
     {
-        super(bcHelper, c, createBasicConstraints(c), createKeyUsage(c));
+        super(bcHelper, c, createBasicConstraints(c), createKeyUsage(c), createSigAlgName(c), createSigAlgParams(c));
     }
 
     public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException
@@ -258,7 +259,8 @@
             encoding = null;
         }
 
-        X509CertificateInternal temp = new X509CertificateInternal(bcHelper, c, basicConstraints, keyUsage, encoding);
+        X509CertificateInternal temp = new X509CertificateInternal(bcHelper, c, basicConstraints, keyUsage, sigAlgName,
+            sigAlgParams, encoding);
 
         synchronized (cacheLock)
         {
@@ -319,4 +321,34 @@
             throw new CertificateParsingException("cannot construct KeyUsage: " + e);
         }
     }
+
+    private static String createSigAlgName(org.bouncycastle.asn1.x509.Certificate c) throws CertificateParsingException
+    {
+        try
+        {
+            return X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm());
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct SigAlgName: " + e);
+        }
+    }
+
+    private static byte[] createSigAlgParams(org.bouncycastle.asn1.x509.Certificate c) throws CertificateParsingException
+    {
+        try
+        {
+            ASN1Encodable parameters = c.getSignatureAlgorithm().getParameters();
+            if (null == parameters)
+            {
+                return null;
+            }
+
+            return parameters.toASN1Primitive().getEncoded(ASN1Encoding.DER);
+        }
+        catch (Exception e)
+        {
+            throw new CertificateParsingException("cannot construct SigAlgParams: " + e);
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
index d94dd16..bc00466 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509SignatureUtil.java
@@ -10,22 +10,43 @@
 import java.security.Signature;
 import java.security.SignatureException;
 import java.security.spec.PSSParameterSpec;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Null;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
+import org.bouncycastle.asn1.misc.MiscObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.jcajce.util.MessageDigestUtils;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
 
 class X509SignatureUtil
 {
-    private static final ASN1Null       derNull = DERNull.INSTANCE;
+    private static final Map<ASN1ObjectIdentifier, String> algNames = new HashMap<ASN1ObjectIdentifier, String>();
+
+    static
+    {
+        algNames.put(EdECObjectIdentifiers.id_Ed25519, "Ed25519");
+        algNames.put(EdECObjectIdentifiers.id_Ed448, "Ed448");
+        algNames.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1withDSA");
+        algNames.put(X9ObjectIdentifiers.id_dsa_with_sha1, "SHA1withDSA");
+    }
+
+    private static final ASN1Null derNull = DERNull.INSTANCE;
+
+    static boolean isCompositeAlgorithm(AlgorithmIdentifier algorithmIdentifier)
+    {
+        return MiscObjectIdentifiers.id_alg_composite.equals(algorithmIdentifier.getAlgorithm());
+    }
 
     static void setSignatureParameters(
         Signature signature,
@@ -34,8 +55,8 @@
     {
         if (params != null && !derNull.equals(params))
         {
-            AlgorithmParameters  sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), signature.getProvider());
-            
+            AlgorithmParameters sigParams = AlgorithmParameters.getInstance(signature.getAlgorithm(), signature.getProvider());
+
             try
             {
                 sigParams.init(params.toASN1Primitive().getEncoded());
@@ -44,7 +65,7 @@
             {
                 throw new SignatureException("IOException decoding parameters: " + e.getMessage());
             }
-            
+
             if (signature.getAlgorithm().endsWith("MGF1"))
             {
                 try
@@ -58,57 +79,38 @@
             }
         }
     }
-    
+
     static String getSignatureName(
-        AlgorithmIdentifier sigAlgId) 
+        AlgorithmIdentifier sigAlgId)
     {
         ASN1Encodable params = sigAlgId.getParameters();
-        
+
         if (params != null && !derNull.equals(params))
         {
             if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
             {
                 RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
-                
+
                 return getDigestAlgName(rsaParams.getHashAlgorithm().getAlgorithm()) + "withRSAandMGF1";
             }
             if (sigAlgId.getAlgorithm().equals(X9ObjectIdentifiers.ecdsa_with_SHA2))
             {
                 ASN1Sequence ecDsaParams = ASN1Sequence.getInstance(params);
-                
+
                 return getDigestAlgName((ASN1ObjectIdentifier)ecDsaParams.getObjectAt(0)) + "withECDSA";
             }
         }
 
-        Provider prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
-
-        if (prov != null)
+        // deal with the "weird" ones.
+        String algName = (String)algNames.get(sigAlgId.getAlgorithm());
+        if (algName != null)
         {
-            String      algName = prov.getProperty("Alg.Alias.Signature." + sigAlgId.getAlgorithm().getId());
-
-            if (algName != null)
-            {
-                return algName;
-            }
+            return algName;
         }
 
-        Provider[] provs = Security.getProviders();
-
-        //
-        // search every provider looking for a real algorithm
-        //
-        for (int i = 0; i != provs.length; i++)
-        {
-            String algName = provs[i].getProperty("Alg.Alias.Signature." + sigAlgId.getAlgorithm().getId());
-            if (algName != null)
-            {
-                return algName;
-            }
-        }
-
-        return sigAlgId.getAlgorithm().getId();
+        return findAlgName(sigAlgId.getAlgorithm());
     }
-    
+
     /**
      * Return the digest algorithm using one of the standard JCA string
      * representations rather the the algorithm identifier (if possible).
@@ -124,6 +126,79 @@
             return name.substring(0, dIndex) + name.substring(dIndex + 1);
         }
 
-        return MessageDigestUtils.getDigestName(digestAlgOID);
+        return name;
     }
+
+    private static String findAlgName(ASN1ObjectIdentifier algOid)
+    {
+        Provider prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
+
+        if (prov != null)
+        {
+            String algName = lookupAlg(prov, algOid);
+            if (algName != null)
+            {
+                return algName;
+            }
+        }
+
+        Provider[] provs = Security.getProviders();
+
+        for (int i = 0; i != provs.length; i++)
+        {
+            if (prov != provs[i])
+            {
+                String algName = lookupAlg(provs[i], algOid);
+                if (algName != null)
+                {
+                    return algName;
+                }
+            }
+        }
+
+        return algOid.getId();
+    }
+
+    private static String lookupAlg(Provider prov, ASN1ObjectIdentifier algOid)
+    {
+        String algName = prov.getProperty("Alg.Alias.Signature." + algOid);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        algName = prov.getProperty("Alg.Alias.Signature.OID." + algOid);
+
+        if (algName != null)
+        {
+            return algName;
+        }
+
+        return null;
+    }
+
+    static void prettyPrintSignature(byte[] sig, StringBuffer buf, String nl)
+    {
+        if (sig.length > 20)
+        {
+            buf.append("            Signature: ").append(Hex.toHexString(sig, 0, 20)).append(nl);
+            for (int i = 20; i < sig.length; i += 20)
+            {
+                if (i < sig.length - 20)
+                {
+                    buf.append("                       ").append(Hex.toHexString(sig, i, 20)).append(nl);
+                }
+                else
+                {
+                    buf.append("                       ").append(Hex.toHexString(sig, i, sig.length - i)).append(nl);
+                }
+            }
+        }
+        else
+        {
+            buf.append("            Signature: ").append(Hex.toHexString(sig)).append(nl);
+        }
+    }
+
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java
index 768df66..9818f86 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/config/ConfigurableProvider.java
@@ -51,5 +51,7 @@
 
     void addKeyInfoConverter(ASN1ObjectIdentifier oid, AsymmetricKeyInfoConverter keyInfoConverter);
 
+    AsymmetricKeyInfoConverter getKeyInfoConverter(ASN1ObjectIdentifier oid);
+
     void addAttributes(String key, Map<String, String> attributeMap);
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java
index 3c5b78d..b195c91 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/BCMessageDigest.java
@@ -3,11 +3,13 @@
 import java.security.MessageDigest;
 
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Xof;
 
 public class BCMessageDigest
     extends MessageDigest
 {
     protected Digest  digest;
+    protected int     digestSize;
 
     protected BCMessageDigest(
         Digest digest)
@@ -15,6 +17,16 @@
         super(digest.getAlgorithmName());
 
         this.digest = digest;
+        this.digestSize = digest.getDigestSize();
+    }
+
+    protected BCMessageDigest(
+        Xof digest, int outputSize)
+    {
+        super(digest.getAlgorithmName());
+
+        this.digest = digest;
+        this.digestSize = outputSize / 8;
     }
 
     public void engineReset() 
@@ -36,9 +48,14 @@
         digest.update(input, offset, len);
     }
 
+    public int engineGetDigestLength()
+    {
+        return digestSize;
+    }
+
     public byte[] engineDigest() 
     {
-        byte[]  digestBytes = new byte[digest.getDigestSize()];
+        byte[]  digestBytes = new byte[digestSize];
 
         digest.doFinal(digestBytes, 0);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SHA3.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SHA3.java
index 8f048b1..e8b6a8b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SHA3.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SHA3.java
@@ -2,7 +2,9 @@
 
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.crypto.Xof;
 import org.bouncycastle.crypto.digests.SHA3Digest;
+import org.bouncycastle.crypto.digests.SHAKEDigest;
 import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
 import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
@@ -34,6 +36,34 @@
         }
     }
 
+    static public class DigestSHAKE
+        extends BCMessageDigest
+        implements Cloneable
+    {
+        public DigestSHAKE(int type, int size)
+        {
+            super(new SHAKEDigest(type), size);
+        }
+
+        public byte[] engineDigest()
+        {
+            byte[]  digestBytes = new byte[digestSize];
+
+            ((Xof)digest).doFinal(digestBytes, 0, digestSize);
+    
+            return digestBytes;
+        }
+
+        public Object clone()
+            throws CloneNotSupportedException
+        {
+            BCMessageDigest d = (BCMessageDigest)super.clone();
+            d.digest = new SHAKEDigest((SHAKEDigest)digest);
+
+            return d;
+        }
+    }
+
     public static class HashMacSHA3
         extends BaseMac
     {
@@ -88,6 +118,24 @@
         }
     }
 
+    static public class DigestShake128_256
+        extends DigestSHAKE
+    {
+        public DigestShake128_256()
+        {
+            super(128, 256);
+        }
+    }
+
+    static public class DigestShake256_512
+        extends DigestSHAKE
+    {
+        public DigestShake256_512()
+        {
+            super(256, 512);
+        }
+    }
+
     static public class HashMac224
         extends HashMacSHA3
     {
@@ -179,6 +227,12 @@
             provider.addAlgorithm("MessageDigest", NISTObjectIdentifiers.id_sha3_256, PREFIX + "$Digest256");
             provider.addAlgorithm("MessageDigest", NISTObjectIdentifiers.id_sha3_384, PREFIX + "$Digest384");
             provider.addAlgorithm("MessageDigest", NISTObjectIdentifiers.id_sha3_512, PREFIX + "$Digest512");
+            provider.addAlgorithm("MessageDigest.SHAKE256-512", PREFIX + "$DigestShake256_512");
+            provider.addAlgorithm("MessageDigest.SHAKE128-256", PREFIX + "$DigestShake128_256");
+            provider.addAlgorithm("MessageDigest", NISTObjectIdentifiers.id_shake256, PREFIX + "$DigestShake256_512");
+            provider.addAlgorithm("MessageDigest", NISTObjectIdentifiers.id_shake128, PREFIX + "$DigestShake128_256");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHAKE256", "SHAKE256-512");
+            provider.addAlgorithm("Alg.Alias.MessageDigest.SHAKE128", "SHAKE128-256");
 
             addHMACAlgorithm(provider, "SHA3-224", PREFIX + "$HashMac224",  PREFIX + "$KeyGenerator224");
             addHMACAlias(provider, "SHA3-224", NISTObjectIdentifiers.id_hmacWithSHA3_224);
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SM3.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SM3.java
index e93facc..863d9f4 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SM3.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/digest/SM3.java
@@ -1,8 +1,12 @@
 package org.bouncycastle.jcajce.provider.digest;
 
 import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
+import org.bouncycastle.crypto.CipherKeyGenerator;
 import org.bouncycastle.crypto.digests.SM3Digest;
+import org.bouncycastle.crypto.macs.HMac;
 import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
 
 public class SM3
 {
@@ -29,6 +33,27 @@
         }
     }
 
+    /**
+     * SM3 HMac
+     */
+    public static class HashMac
+        extends BaseMac
+    {
+        public HashMac()
+        {
+            super(new HMac(new SM3Digest()));
+        }
+    }
+
+    public static class KeyGenerator
+        extends BaseKeyGenerator
+    {
+        public KeyGenerator()
+        {
+            super("HMACSM3", 256, new CipherKeyGenerator());
+        }
+    }
+
     public static class Mappings
         extends DigestAlgorithmProvider
     {
@@ -44,6 +69,9 @@
             provider.addAlgorithm("Alg.Alias.MessageDigest.SM3", "SM3");
             provider.addAlgorithm("Alg.Alias.MessageDigest.1.2.156.197.1.401", "SM3");  // old draft OID - deprecated
             provider.addAlgorithm("Alg.Alias.MessageDigest." + GMObjectIdentifiers.sm3, "SM3");
+
+            addHMACAlgorithm(provider, "SM3", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
+            addHMACAlias(provider, "SM3", GMObjectIdentifiers.hmac_sm3);
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java
index dead545..5d6cfdb 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/drbg/DRBG.java
@@ -27,6 +27,21 @@
 import org.bouncycastle.util.Properties;
 import org.bouncycastle.util.Strings;
 
+/**
+ * <b>DRBG Configuration</b><br/>
+ * <p>org.bouncycastle.drbg.gather_pause_secs - is to stop the entropy collection thread from grabbing all
+ * available entropy on the system. The original motivation for the hybrid infrastructure was virtual machines
+ * sometimes produce very few bits of entropy a second, the original approach (which "worked" at least for BC) was
+ * to just read on the second thread and allow things to progress around it, but it did tend to hog the system
+ * if other processes were using /dev/random. By default the thread will pause for 5 seconds between 64 bit reads,
+ * increasing this time will reduce the demands on the system entropy pool. Ideally the pause will be set to large
+ * enough to allow everyone to work together, but small enough to ensure the provider's DRBG is being regularly
+ * reseeded.
+ * </p>
+ * <p>org.bouncycastle.drbg.entropysource - is the class name for an implementation of EntropySourceProvider.
+ * For example, one could be provided which just reads directly from /dev/random and the extra infrastructure used here
+ * could be avoided.</p>
+ */
 public class DRBG
 {
     private static final String PREFIX = DRBG.class.getName();
@@ -133,14 +148,14 @@
             }
             catch (Exception e)
             {
-                return new SecureRandom();  // we're desperate, it's worth a try.
+                return new CoreSecureRandom(findSource());
             }
         }
     }
 
     private static EntropySourceProvider createEntropySource()
     {
-        final String sourceClass = System.getProperty("org.bouncycastle.drbg.entropysource");
+        final String sourceClass = Properties.getPropertyValue("org.bouncycastle.drbg.entropysource");
 
         return AccessController.doPrivileged(new PrivilegedAction<EntropySourceProvider>()
         {
@@ -162,7 +177,7 @@
 
     private static SecureRandom createBaseRandom(boolean isPredictionResistant)
     {
-        if (System.getProperty("org.bouncycastle.drbg.entropysource") != null)
+        if (Properties.getPropertyValue("org.bouncycastle.drbg.entropysource") != null)
         {
             EntropySourceProvider entropyProvider = createEntropySource();
 
@@ -292,7 +307,7 @@
                     }
                     catch (IOException e)
                     {
-                        throw new InternalError("unable to open random source");
+                        throw new IllegalStateException("unable to open random source");
                     }
                 }
             });
@@ -440,7 +455,10 @@
 
                 if (!scheduled.getAndSet(true))
                 {
-                    new Thread(new EntropyGatherer(byteLength)).start();
+                    // don't try to be clever here - things change in Java 11!
+                    Thread gatherer = new Thread(new EntropyGatherer(byteLength));
+                    gatherer.setDaemon(true);
+                    gatherer.start();
                 }
 
                 return seed;
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java
index e7567e5..aecfe75 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/bc/BcKeyStoreSpi.java
@@ -141,7 +141,6 @@
 
             byte[] salt = new byte[KEY_SALT_SIZE];
 
-            random.setSeed(System.currentTimeMillis());
             random.nextBytes(salt);
 
             int iterationCount = MIN_ITERATIONS + (random.nextInt() & 0x3ff);
@@ -678,7 +677,7 @@
         }
         catch (Exception e)
         {
-            throw new KeyStoreException(e.toString());
+            throw new BCKeyStoreException(e.toString(), e);
         }
     }
 
@@ -1064,4 +1063,21 @@
             super(1);
         }
     }
+
+    private static class BCKeyStoreException
+        extends KeyStoreException
+    {
+        private final Exception cause;
+
+        public BCKeyStoreException(String msg, Exception cause)
+        {
+            super(msg);
+            this.cause = cause;
+        }
+
+        public Throwable getCause()
+        {
+            return cause;
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
index a0e6dd5..4e3540d 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/keystore/pkcs12/PKCS12KeyStoreSpi.java
@@ -393,26 +393,16 @@
                 X509Certificate x509c = (X509Certificate)c;
                 Certificate nextC = null;
 
-                byte[] bytes = x509c.getExtensionValue(Extension.authorityKeyIdentifier.getId());
-                if (bytes != null)
+                byte[] akiBytes = x509c.getExtensionValue(Extension.authorityKeyIdentifier.getId());
+                if (akiBytes != null)
                 {
-                    try
+                    ASN1OctetString akiValue = ASN1OctetString.getInstance(akiBytes);
+                    AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(akiValue.getOctets());
+
+                    byte[] keyID = aki.getKeyIdentifier();
+                    if (null != keyID)
                     {
-                        ASN1InputStream aIn = new ASN1InputStream(bytes);
-
-                        byte[] authBytes = ASN1OctetString.getInstance(aIn.readObject()).getOctets();
-                        aIn = new ASN1InputStream(authBytes);
-
-                        AuthorityKeyIdentifier id = AuthorityKeyIdentifier.getInstance(aIn.readObject());
-                        if (id.getKeyIdentifier() != null)
-                        {
-                            nextC = (Certificate)chainCerts.get(new CertId(id.getKeyIdentifier()));
-                        }
-
-                    }
-                    catch (IOException e)
-                    {
-                        throw new RuntimeException(e.toString());
+                        nextC = (Certificate)chainCerts.get(new CertId(keyID));
                     }
                 }
 
@@ -846,14 +836,11 @@
                 throw new IOException("error constructing MAC: " + e.toString());
             }
         }
-        else
+        else if (password != null)
         {
             if (!Properties.isOverrideSet("org.bouncycastle.pkcs12.ignore_useless_passwd"))
             {
-                if (password != null)
-                {
-                    throw new IOException("password supplied for keystore that does not require one");
-                }
+                throw new IOException("password supplied for keystore that does not require one");
             }
         }
 
@@ -862,17 +849,16 @@
 
         if (info.getContentType().equals(data))
         {
-            bIn = new ASN1InputStream(((ASN1OctetString)info.getContent()).getOctets());
-
-            AuthenticatedSafe authSafe = AuthenticatedSafe.getInstance(bIn.readObject());
+            ASN1OctetString content = ASN1OctetString.getInstance(info.getContent());
+            AuthenticatedSafe authSafe = AuthenticatedSafe.getInstance(content.getOctets());
             ContentInfo[] c = authSafe.getContentInfo();
 
             for (int i = 0; i != c.length; i++)
             {
                 if (c[i].getContentType().equals(data))
                 {
-                    ASN1InputStream dIn = new ASN1InputStream(ASN1OctetString.getInstance(c[i].getContent()).getOctets());
-                    ASN1Sequence seq = ASN1Sequence.getInstance(dIn.readObject());
+                    ASN1OctetString authSafeContent = ASN1OctetString.getInstance(c[i].getContent());
+                    ASN1Sequence seq = ASN1Sequence.getInstance(authSafeContent.getOctets());
 
                     for (int j = 0; j != seq.size(); j++)
                     {
@@ -969,7 +955,7 @@
                     EncryptedData d = EncryptedData.getInstance(c[i].getContent());
                     byte[] octets = cryptData(false, d.getEncryptionAlgorithm(),
                         password, wrongPKCS12Zero, d.getContent().getOctets());
-                    ASN1Sequence seq = (ASN1Sequence)ASN1Primitive.fromByteArray(octets);
+                    ASN1Sequence seq = ASN1Sequence.getInstance(octets);
 
                     for (int j = 0; j != seq.size(); j++)
                     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/AES.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/AES.java
index 83f29f0..a5bfdff 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/AES.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/AES.java
@@ -45,6 +45,7 @@
 import org.bouncycastle.jcajce.provider.symmetric.util.BaseSecretKeyFactory;
 import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
 import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil;
 import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
 import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;
 import org.bouncycastle.jcajce.spec.AEADParameterSpec;
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/ARIA.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/ARIA.java
index 7d9cb45..d9b0c52 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/ARIA.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/ARIA.java
@@ -23,6 +23,7 @@
 import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
 import org.bouncycastle.crypto.macs.GMac;
 import org.bouncycastle.crypto.modes.CBCBlockCipher;
+import org.bouncycastle.crypto.modes.CCMBlockCipher;
 import org.bouncycastle.crypto.modes.CFBBlockCipher;
 import org.bouncycastle.crypto.modes.GCMBlockCipher;
 import org.bouncycastle.crypto.modes.OFBBlockCipher;
@@ -35,6 +36,7 @@
 import org.bouncycastle.jcajce.provider.symmetric.util.BaseSecretKeyFactory;
 import org.bouncycastle.jcajce.provider.symmetric.util.BaseWrapCipher;
 import org.bouncycastle.jcajce.provider.symmetric.util.BlockCipherProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.GcmSpecUtil;
 import org.bouncycastle.jcajce.provider.symmetric.util.IvAlgorithmParameters;
 import org.bouncycastle.jcajce.spec.AEADParameterSpec;
 
@@ -86,6 +88,24 @@
         }
     }
 
+    static public class CCM
+            extends BaseBlockCipher
+    {
+        public CCM()
+        {
+            super(new CCMBlockCipher(new ARIAEngine()), false, 12);
+        }
+    }
+
+    static public class GCM
+            extends BaseBlockCipher
+    {
+        public GCM()
+        {
+            super(new GCMBlockCipher(new ARIAEngine()));
+        }
+    }
+
     public static class Wrap
         extends BaseWrapCipher
     {
@@ -498,23 +518,25 @@
             provider.addAlgorithm("Alg.Alias.SecretKeyFactory", NSRIObjectIdentifiers.id_aria192_cbc, "ARIA");
             provider.addAlgorithm("Alg.Alias.SecretKeyFactory", NSRIObjectIdentifiers.id_aria256_cbc, "ARIA");
 
-            provider.addAlgorithm("AlgorithmParameterGenerator.ARIACCM", PREFIX + "$AlgParamGenCCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria128_ccm, "CCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria192_ccm, "CCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria256_ccm, "CCM");
+            provider.addAlgorithm("AlgorithmParameterGenerator.ARIACCM", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria128_ccm, "ARIACCM");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria192_ccm, "ARIACCM");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria256_ccm, "ARIACCM");
 
+            provider.addAlgorithm("Cipher.ARIACCM", PREFIX + "$CCM");
             provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria128_ccm, "CCM");
             provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria192_ccm, "CCM");
             provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria256_ccm, "CCM");
 
-            provider.addAlgorithm("AlgorithmParameterGenerator.ARIAGCM", PREFIX + "$AlgParamGenGCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria128_gcm, "GCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria192_gcm, "GCM");
-            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria256_gcm, "GCM");
+            provider.addAlgorithm("AlgorithmParameterGenerator.ARIAGCM", PREFIX + "$AlgParamGen");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria128_gcm, "ARIAGCM");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria192_gcm, "ARIAGCM");
+            provider.addAlgorithm("Alg.Alias.AlgorithmParameterGenerator." + NSRIObjectIdentifiers.id_aria256_gcm, "ARIAGCM");
 
-            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria128_gcm, "GCM");
-            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria192_gcm, "GCM");
-            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria256_gcm, "GCM");
+            provider.addAlgorithm("Cipher.ARIAGCM", PREFIX + "$GCM");
+            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria128_gcm, "ARIAGCM");
+            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria192_gcm, "ARIAGCM");
+            provider.addAlgorithm("Alg.Alias.Cipher", NSRIObjectIdentifiers.id_aria256_gcm, "ARIAGCM");
             
             addGMacAlgorithm(provider, "ARIA", PREFIX + "$GMAC", PREFIX + "$KeyGen");
             addPoly1305Algorithm(provider, "ARIA", PREFIX + "$Poly1305", PREFIX + "$Poly1305KeyGen");
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/CAST5.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/CAST5.java
index 7c29095..13ced70 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/CAST5.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/CAST5.java
@@ -130,7 +130,7 @@
             Class paramSpec)
             throws InvalidParameterSpecException
         {
-            if (paramSpec == IvParameterSpec.class)
+            if (paramSpec == IvParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
             {
                 return new IvParameterSpec(iv);
             }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DESede.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DESede.java
index d433abe..66368a6 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DESede.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DESede.java
@@ -378,10 +378,8 @@
             if (provider.hasAlgorithm("MessageDigest", "SHA-1"))
             {
                 provider.addAlgorithm("Cipher.PBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES3Key");
-                provider.addAlgorithm("Cipher.BROKENPBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$BrokePBEWithSHAAndDES3Key");
-                provider.addAlgorithm("Cipher.OLDPBEWITHSHAAND3-KEYTRIPLEDES-CBC", PREFIX + "$OldPBEWithSHAAndDES3Key");
                 provider.addAlgorithm("Cipher.PBEWITHSHAAND2-KEYTRIPLEDES-CBC", PREFIX + "$PBEWithSHAAndDES2Key");
-                provider.addAlgorithm("Cipher.BROKENPBEWITHSHAAND2-KEYTRIPLEDES-CBC", PREFIX + "$BrokePBEWithSHAAndDES2Key");
+
                 provider.addAlgorithm("Alg.Alias.Cipher", PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC, "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
                 provider.addAlgorithm("Alg.Alias.Cipher", PKCSObjectIdentifiers.pbeWithSHAAnd2_KeyTripleDES_CBC, "PBEWITHSHAAND2-KEYTRIPLEDES-CBC");
                 provider.addAlgorithm("Alg.Alias.Cipher.PBEWITHSHA1ANDDESEDE", "PBEWITHSHAAND3-KEYTRIPLEDES-CBC");
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DSTU7624.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DSTU7624.java
index 4ef48cb..85a0b5b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DSTU7624.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/DSTU7624.java
@@ -430,7 +430,7 @@
     public static class AlgParamGen128
         extends AlgParamGen
     {
-        AlgParamGen128()
+        public AlgParamGen128()
         {
             super(128);
         }
@@ -439,7 +439,7 @@
     public static class AlgParamGen256
         extends AlgParamGen
     {
-        AlgParamGen256()
+        public AlgParamGen256()
         {
             super(256);
         }
@@ -448,7 +448,7 @@
     public static class AlgParamGen512
         extends AlgParamGen
     {
-        AlgParamGen512()
+        public AlgParamGen512()
         {
             super(512);
         }
@@ -475,7 +475,7 @@
         public void configure(ConfigurableProvider provider)
         {
 
-            provider.addAlgorithm("AlgorithmParameters.DSTU7624", PREFIX + "$AlgParams128");
+            provider.addAlgorithm("AlgorithmParameters.DSTU7624", PREFIX + "$AlgParams");
             provider.addAlgorithm("AlgorithmParameters", UAObjectIdentifiers.dstu7624cbc_128, PREFIX + "$AlgParams");
             provider.addAlgorithm("AlgorithmParameters",  UAObjectIdentifiers.dstu7624cbc_256, PREFIX + "$AlgParams");
             provider.addAlgorithm("AlgorithmParameters",  UAObjectIdentifiers.dstu7624cbc_512, PREFIX + "$AlgParams");
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/IDEA.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/IDEA.java
index f4d16f8..3b5fc91 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/IDEA.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/IDEA.java
@@ -150,7 +150,7 @@
             Class paramSpec)
             throws InvalidParameterSpecException
         {
-            if (paramSpec == IvParameterSpec.class)
+            if (paramSpec == IvParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
             {
                 return new IvParameterSpec(iv);
             }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/OpenSSLPBKDF.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/OpenSSLPBKDF.java
index 5888adf..d2135c5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/OpenSSLPBKDF.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/OpenSSLPBKDF.java
@@ -60,7 +60,7 @@
 
                 OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator();
 
-                pGen.init(Strings.toByteArray(pbeSpec.getPassword()), pbeSpec.getSalt());
+                pGen.init(Strings.toUTF8ByteArray(pbeSpec.getPassword()), pbeSpec.getSalt());
 
                 return new SecretKeySpec(((KeyParameter)pGen.generateDerivedParameters(pbeSpec.getKeyLength())).getKey(), "OpenSSLPBKDF");
             }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF1.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF1.java
index 122f918..e7e0a84 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF1.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF1.java
@@ -52,7 +52,7 @@
             Class paramSpec)
             throws InvalidParameterSpecException
         {
-            if (paramSpec == PBEParameterSpec.class)
+            if (paramSpec == PBEParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
             {
                 return new PBEParameterSpec(params.getSalt(),
                                 params.getIterationCount().intValue());
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java
index c68dfb0..8e85959 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPBKDF2.java
@@ -88,7 +88,7 @@
             Class paramSpec)
             throws InvalidParameterSpecException
         {
-            if (paramSpec == PBEParameterSpec.class)
+            if (paramSpec == PBEParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
             {
                 return new PBEParameterSpec(params.getSalt(),
                     params.getIterationCount().intValue());
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java
index 9be3c99..3b6efbb 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/PBEPKCS12.java
@@ -52,7 +52,7 @@
             Class paramSpec)
             throws InvalidParameterSpecException
         {
-            if (paramSpec == PBEParameterSpec.class)
+            if (paramSpec == PBEParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
             {
                 return new PBEParameterSpec(params.getIV(),
                     params.getIterations().intValue());
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/RC2.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/RC2.java
index 36b2068..e38b079 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/RC2.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/RC2.java
@@ -361,7 +361,7 @@
                 }
             }
 
-            if (paramSpec == IvParameterSpec.class || paramSpec == AlgorithmParameterSpec.class)
+            if (paramSpec == IvParameterSpec.class)
             {
                 return new IvParameterSpec(iv);
             }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SCRYPT.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SCRYPT.java
index 82e4179..6dd3d9b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SCRYPT.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SCRYPT.java
@@ -68,7 +68,7 @@
                         pbeSpec.getCostParameter(), pbeSpec.getBlockSize(), pbeSpec.getParallelizationParameter(),
                         pbeSpec.getKeyLength() / 8));
 
-                return new BCPBEKey(this.algName, pbeSpec, param);
+                return new BCPBEKey(this.algName, param);
             }
 
             throw new InvalidKeySpecException("Invalid KeySpec");
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SipHash128.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SipHash128.java
new file mode 100644
index 0000000..018a802
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/SipHash128.java
@@ -0,0 +1,62 @@
+package org.bouncycastle.jcajce.provider.symmetric;
+
+import org.bouncycastle.crypto.CipherKeyGenerator;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
+import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
+import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
+
+public final class SipHash128
+{
+    private SipHash128()
+    {
+    }
+
+    public static class Mac24
+        extends BaseMac
+    {
+        public Mac24()
+        {
+            super(new org.bouncycastle.crypto.macs.SipHash128());
+        }
+    }
+
+    public static class Mac48
+        extends BaseMac
+    {
+        public Mac48()
+        {
+            super(new org.bouncycastle.crypto.macs.SipHash128(4, 8));
+        }
+    }
+
+    public static class KeyGen
+        extends BaseKeyGenerator
+    {
+        public KeyGen()
+        {
+            super("SipHash128", 128, new CipherKeyGenerator());
+        }
+    }
+
+    public static class Mappings
+        extends AlgorithmProvider
+    {
+        private static final String PREFIX = SipHash128.class.getName();
+
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("Mac.SIPHASH128-2-4", PREFIX + "$Mac24");
+            provider.addAlgorithm("Alg.Alias.Mac.SIPHASH128", "SIPHASH128-2-4");
+            provider.addAlgorithm("Mac.SIPHASH128-4-8", PREFIX + "$Mac48");
+
+            provider.addAlgorithm("KeyGenerator.SIPHASH128", PREFIX + "$KeyGen");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.SIPHASH128-2-4", "SIPHASH128");
+            provider.addAlgorithm("Alg.Alias.KeyGenerator.SIPHASH128-4-8", "SIPHASH128");
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java
index 85113e1..d4756df 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BCPBEKey.java
@@ -1,27 +1,36 @@
 package org.bouncycastle.jcajce.provider.symmetric.util;
 
-import java.security.spec.KeySpec;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.crypto.interfaces.PBEKey;
 import javax.crypto.spec.PBEKeySpec;
+import javax.security.auth.Destroyable;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.PBEParametersGenerator;
 import org.bouncycastle.crypto.params.KeyParameter;
 import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
 
 public class BCPBEKey
-    implements PBEKey
+    implements PBEKey, Destroyable
 {
+    private final AtomicBoolean hasBeenDestroyed = new AtomicBoolean(false);
+
     String              algorithm;
     ASN1ObjectIdentifier oid;
     int                 type;
     int                 digest;
     int                 keySize;
     int                 ivSize;
-    CipherParameters    param;
-    PBEKeySpec          pbeKeySpec;
+
+    private final char[] password;
+    private final byte[] salt;
+    private final int iterationCount;
+
+    private final CipherParameters    param;
+
     boolean             tryWrong = false;
 
     /**
@@ -43,19 +52,25 @@
         this.digest = digest;
         this.keySize = keySize;
         this.ivSize = ivSize;
-        this.pbeKeySpec = pbeKeySpec;
+        this.password = pbeKeySpec.getPassword();
+        this.iterationCount = pbeKeySpec.getIterationCount();
+        this.salt = pbeKeySpec.getSalt();
         this.param = param;
     }
 
-    public BCPBEKey(String algName,
-                    KeySpec pbeSpec, CipherParameters param)
+    public BCPBEKey(String algName, CipherParameters param)
     {
         this.algorithm = algName;
         this.param = param;
+        this.password = null;
+        this.iterationCount = -1;
+        this.salt = null;
     }
 
     public String getAlgorithm()
     {
+        checkDestroyed(this);
+
         return algorithm;
     }
 
@@ -66,6 +81,8 @@
 
     public byte[] getEncoded()
     {
+        checkDestroyed(this);
+
         if (param != null)
         {
             KeyParameter    kParam;
@@ -85,41 +102,51 @@
         {
             if (type == PBE.PKCS12)
             {
-                return PBEParametersGenerator.PKCS12PasswordToBytes(pbeKeySpec.getPassword());
+                return PBEParametersGenerator.PKCS12PasswordToBytes(password);
             }
             else if (type == PBE.PKCS5S2_UTF8)
             {
-                return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(pbeKeySpec.getPassword());
+                return PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password);
             }
             else
             {   
-                return PBEParametersGenerator.PKCS5PasswordToBytes(pbeKeySpec.getPassword());
+                return PBEParametersGenerator.PKCS5PasswordToBytes(password);
             }
         }
     }
     
     int getType()
     {
+        checkDestroyed(this);
+
         return type;
     }
     
     int getDigest()
     {
+        checkDestroyed(this);
+
         return digest;
     }
     
     int getKeySize()
     {
+        checkDestroyed(this);
+
         return keySize;
     }
     
     public int getIvSize()
     {
+        checkDestroyed(this);
+
         return ivSize;
     }
     
     public CipherParameters getParam()
     {
+        checkDestroyed(this);
+
         return param;
     }
 
@@ -128,7 +155,14 @@
      */
     public char[] getPassword()
     {
-        return pbeKeySpec.getPassword();
+        checkDestroyed(this);
+
+        if (password == null)
+        {
+            throw new IllegalStateException("no password available");
+        }
+
+        return Arrays.clone(password);
     }
 
     /* (non-Javadoc)
@@ -136,7 +170,9 @@
      */
     public byte[] getSalt()
     {
-        return pbeKeySpec.getSalt();
+        checkDestroyed(this);
+
+        return Arrays.clone(salt);
     }
 
     /* (non-Javadoc)
@@ -144,11 +180,15 @@
      */
     public int getIterationCount()
     {
-        return pbeKeySpec.getIterationCount();
+        checkDestroyed(this);
+
+        return iterationCount;
     }
     
     public ASN1ObjectIdentifier getOID()
     {
+        checkDestroyed(this);
+
         return oid;
     }
     
@@ -161,4 +201,32 @@
     {
         return tryWrong;
     }
+
+    public void destroy()
+    {
+        if (!hasBeenDestroyed.getAndSet(true))
+        {
+            if (password != null)
+            {
+                Arrays.fill(password, (char)0);
+            }
+            if (salt != null)
+            {
+                Arrays.fill(salt, (byte)0);
+            }
+        }
+    }
+
+    public boolean isDestroyed()
+    {
+        return hasBeenDestroyed.get();
+    }
+
+    static void checkDestroyed(Destroyable destroyable)
+    {
+        if (destroyable.isDestroyed())
+        {
+            throw new IllegalStateException("key has been destroyed");
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java
index 18950f1..6fae084 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseBlockCipher.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.jcajce.provider.symmetric.util;
 
 import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
 import java.security.AlgorithmParameters;
 import java.security.InvalidAlgorithmParameterException;
@@ -87,34 +86,34 @@
     //
     // specs we can handle.
     //
-    private Class[]                 availableSpecs =
-                                    {
-                                        RC2ParameterSpec.class,
-                                        RC5ParameterSpec.class,
-                                        gcmSpecClass,
-                                        GOST28147ParameterSpec.class,
-                                        IvParameterSpec.class,
-                                        PBEParameterSpec.class
-                                    };
+    private Class[] availableSpecs =
+        {
+            RC2ParameterSpec.class,
+            RC5ParameterSpec.class,
+            gcmSpecClass,
+            GOST28147ParameterSpec.class,
+            IvParameterSpec.class,
+            PBEParameterSpec.class
+        };
 
-    private BlockCipher             baseEngine;
-    private BlockCipherProvider     engineProvider;
-    private GenericBlockCipher      cipher;
-    private ParametersWithIV        ivParam;
-    private AEADParameters          aeadParams;
+    private BlockCipher baseEngine;
+    private BlockCipherProvider engineProvider;
+    private GenericBlockCipher cipher;
+    private ParametersWithIV ivParam;
+    private AEADParameters aeadParams;
 
     private int keySizeInBits;
     private int scheme = -1;
     private int digest;
 
-    private int                     ivLength = 0;
+    private int ivLength = 0;
 
-    private boolean                 padded;
-    private boolean                 fixedIv = true;
-    private PBEParameterSpec        pbeSpec = null;
-    private String                  pbeAlgorithm = null;
+    private boolean padded;
+    private boolean fixedIv = true;
+    private PBEParameterSpec pbeSpec = null;
+    private String pbeAlgorithm = null;
 
-    private String                  modeName = null;
+    private String modeName = null;
 
     protected BaseBlockCipher(
         BlockCipher engine)
@@ -238,13 +237,13 @@
     }
 
     protected int engineGetKeySize(
-        Key     key)
+        Key key)
     {
         return key.getEncoded().length * 8;
     }
 
     protected int engineGetOutputSize(
-        int     inputLen)
+        int inputLen)
     {
         return cipher.getOutputSize(inputLen);
     }
@@ -295,7 +294,7 @@
             }
             else if (ivParam != null)
             {
-                String  name = cipher.getUnderlyingCipher().getAlgorithmName();
+                String name = cipher.getUnderlyingCipher().getAlgorithmName();
 
                 if (name.indexOf('/') >= 0)
                 {
@@ -318,7 +317,7 @@
     }
 
     protected void engineSetMode(
-        String  mode)
+        String mode)
         throws NoSuchAlgorithmException
     {
         if (baseEngine == null)
@@ -336,7 +335,7 @@
         {
             ivLength = baseEngine.getBlockSize();
             cipher = new BufferedGenericBlockCipher(
-                            new CBCBlockCipher(baseEngine));
+                new CBCBlockCipher(baseEngine));
         }
         else if (modeName.startsWith("OFB"))
         {
@@ -346,12 +345,12 @@
                 int wordSize = Integer.parseInt(modeName.substring(3));
 
                 cipher = new BufferedGenericBlockCipher(
-                                new OFBBlockCipher(baseEngine, wordSize));
+                    new OFBBlockCipher(baseEngine, wordSize));
             }
             else
             {
                 cipher = new BufferedGenericBlockCipher(
-                        new OFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
+                    new OFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
             }
         }
         else if (modeName.startsWith("CFB"))
@@ -362,29 +361,34 @@
                 int wordSize = Integer.parseInt(modeName.substring(3));
 
                 cipher = new BufferedGenericBlockCipher(
-                                new CFBBlockCipher(baseEngine, wordSize));
+                    new CFBBlockCipher(baseEngine, wordSize));
             }
             else
             {
                 cipher = new BufferedGenericBlockCipher(
-                        new CFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
+                    new CFBBlockCipher(baseEngine, 8 * baseEngine.getBlockSize()));
             }
         }
-        else if (modeName.startsWith("PGP"))
+        else if (modeName.startsWith("PGPCFB"))
         {
-            boolean inlineIV = modeName.equalsIgnoreCase("PGPCFBwithIV");
+            boolean inlineIV = modeName.equals("PGPCFBWITHIV");
 
+            if (!inlineIV && modeName.length() != 6)
+            {
+                throw new NoSuchAlgorithmException("no mode support for " + modeName);
+            }
+            
             ivLength = baseEngine.getBlockSize();
             cipher = new BufferedGenericBlockCipher(
                 new PGPCFBBlockCipher(baseEngine, inlineIV));
         }
-        else if (modeName.equalsIgnoreCase("OpenPGPCFB"))
+        else if (modeName.equals("OPENPGPCFB"))
         {
             ivLength = 0;
             cipher = new BufferedGenericBlockCipher(
                 new OpenPGPCFBBlockCipher(baseEngine));
         }
-        else if (modeName.startsWith("SIC"))
+        else if (modeName.equals("SIC"))
         {
             ivLength = baseEngine.getBlockSize();
             if (ivLength < 16)
@@ -393,16 +397,16 @@
             }
             fixedIv = false;
             cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new SICBlockCipher(baseEngine)));
+                new SICBlockCipher(baseEngine)));
         }
-        else if (modeName.startsWith("CTR"))
+        else if (modeName.equals("CTR"))
         {
             ivLength = baseEngine.getBlockSize();
             fixedIv = false;
             if (baseEngine instanceof DSTU7624Engine)
             {
                 cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                                    new KCTRBlockCipher(baseEngine)));
+                    new KCTRBlockCipher(baseEngine)));
             }
             else
             {
@@ -410,24 +414,24 @@
                     new SICBlockCipher(baseEngine)));
             }
         }
-        else if (modeName.startsWith("GOFB"))
+        else if (modeName.equals("GOFB"))
         {
             ivLength = baseEngine.getBlockSize();
             cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new GOFBBlockCipher(baseEngine)));
+                new GOFBBlockCipher(baseEngine)));
         }
-        else if (modeName.startsWith("GCFB"))
+        else if (modeName.equals("GCFB"))
         {
             ivLength = baseEngine.getBlockSize();
             cipher = new BufferedGenericBlockCipher(new BufferedBlockCipher(
-                        new GCFBBlockCipher(baseEngine)));
+                new GCFBBlockCipher(baseEngine)));
         }
-        else if (modeName.startsWith("CTS"))
+        else if (modeName.equals("CTS"))
         {
             ivLength = baseEngine.getBlockSize();
             cipher = new BufferedGenericBlockCipher(new CTSBlockCipher(new CBCBlockCipher(baseEngine)));
         }
-        else if (modeName.startsWith("CCM"))
+        else if (modeName.equals("CCM"))
         {
             ivLength = 12; // CCM nonce 7..13 bytes
             if (baseEngine instanceof DSTU7624Engine)
@@ -439,7 +443,7 @@
                 cipher = new AEADGenericBlockCipher(new CCMBlockCipher(baseEngine));
             }
         }
-        else if (modeName.startsWith("OCB"))
+        else if (modeName.equals("OCB"))
         {
             if (engineProvider != null)
             {
@@ -454,12 +458,12 @@
                 throw new NoSuchAlgorithmException("can't support mode " + mode);
             }
         }
-        else if (modeName.startsWith("EAX"))
+        else if (modeName.equals("EAX"))
         {
             ivLength = baseEngine.getBlockSize();
             cipher = new AEADGenericBlockCipher(new EAXBlockCipher(baseEngine));
         }
-        else if (modeName.startsWith("GCM"))
+        else if (modeName.equals("GCM"))
         {
             ivLength = baseEngine.getBlockSize();
             if (baseEngine instanceof DSTU7624Engine)
@@ -478,15 +482,15 @@
     }
 
     protected void engineSetPadding(
-        String  padding)
-    throws NoSuchPaddingException
+        String padding)
+        throws NoSuchPaddingException
     {
         if (baseEngine == null)
         {
             throw new NoSuchPaddingException("no padding supported for this algorithm");
         }
 
-        String  paddingName = Strings.toUpperCase(padding);
+        String paddingName = Strings.toUpperCase(padding);
 
         if (paddingName.equals("NOPADDING"))
         {
@@ -539,13 +543,13 @@
     }
 
     protected void engineInit(
-        int                     opmode,
-        Key                     key,
-        AlgorithmParameterSpec  params,
-        SecureRandom            random)
+        int opmode,
+        Key key,
+        final AlgorithmParameterSpec params,
+        SecureRandom random)
         throws InvalidKeyException, InvalidAlgorithmParameterException
     {
-        CipherParameters        param;
+        CipherParameters param;
 
         this.pbeSpec = null;
         this.pbeAlgorithm = null;
@@ -583,7 +587,7 @@
                 throw new InvalidKeyException("PKCS12 requires a SecretKey/PBEKey");
             }
 
-            if (params instanceof  PBEParameterSpec)
+            if (params instanceof PBEParameterSpec)
             {
                 pbeSpec = (PBEParameterSpec)params;
             }
@@ -760,10 +764,10 @@
         }
         else if (params instanceof GOST28147ParameterSpec)
         {
-            GOST28147ParameterSpec    gost28147Param = (GOST28147ParameterSpec)params;
+            GOST28147ParameterSpec gost28147Param = (GOST28147ParameterSpec)params;
 
             param = new ParametersWithSBox(
-                       new KeyParameter(key.getEncoded()), ((GOST28147ParameterSpec)params).getSbox());
+                new KeyParameter(key.getEncoded()), ((GOST28147ParameterSpec)params).getSbox());
 
             if (gost28147Param.getIV() != null && ivLength != 0)
             {
@@ -780,7 +784,7 @@
         }
         else if (params instanceof RC2ParameterSpec)
         {
-            RC2ParameterSpec    rc2Param = (RC2ParameterSpec)params;
+            RC2ParameterSpec rc2Param = (RC2ParameterSpec)params;
 
             param = new RC2Parameters(key.getEncoded(), ((RC2ParameterSpec)params).getEffectiveKeyBits());
 
@@ -799,7 +803,7 @@
         }
         else if (params instanceof RC5ParameterSpec)
         {
-            RC5ParameterSpec    rc5Param = (RC5ParameterSpec)params;
+            RC5ParameterSpec rc5Param = (RC5ParameterSpec)params;
 
             param = new RC5Parameters(key.getEncoded(), ((RC5ParameterSpec)params).getRounds());
             if (baseEngine.getAlgorithmName().startsWith("RC5"))
@@ -843,26 +847,17 @@
                 throw new InvalidAlgorithmParameterException("GCMParameterSpec can only be used with AEAD modes.");
             }
 
-            try
+            final KeyParameter keyParam;
+            if (param instanceof ParametersWithIV)
             {
-                Method tLen = gcmSpecClass.getDeclaredMethod("getTLen", new Class[0]);
-                Method iv= gcmSpecClass.getDeclaredMethod("getIV", new Class[0]);
+                keyParam = (KeyParameter)((ParametersWithIV)param).getParameters();
+            }
+            else
+            {
+                keyParam = (KeyParameter)param;
+            }
 
-                KeyParameter keyParam;
-                if (param instanceof ParametersWithIV)
-                {
-                    keyParam = (KeyParameter)((ParametersWithIV)param).getParameters();
-                }
-                else
-                {
-                    keyParam = (KeyParameter)param;
-                }
-                param = aeadParams = new AEADParameters(keyParam, ((Integer)tLen.invoke(params, new Object[0])).intValue(), (byte[])iv.invoke(params, new Object[0]));
-            }
-            catch (Exception e)
-            {
-                throw new InvalidAlgorithmParameterException("Cannot process GCMParameterSpec.");
-            }
+            param = aeadParams = GcmSpecUtil.extractAeadParameters(keyParam, params);
         }
         else if (params != null && !(params instanceof PBEParameterSpec))
         {
@@ -871,7 +866,7 @@
 
         if ((ivLength != 0) && !(param instanceof ParametersWithIV) && !(param instanceof AEADParameters))
         {
-            SecureRandom    ivRandom = random;
+            SecureRandom ivRandom = random;
 
             if (ivRandom == null)
             {
@@ -880,7 +875,7 @@
 
             if ((opmode == Cipher.ENCRYPT_MODE) || (opmode == Cipher.WRAP_MODE))
             {
-                byte[]  iv = new byte[ivLength];
+                byte[] iv = new byte[ivLength];
 
                 ivRandom.nextBytes(iv);
                 param = new ParametersWithIV(param, iv);
@@ -893,7 +888,6 @@
         }
 
 
-
         if (random != null && padded)
         {
             param = new ParametersWithRandom(param, random);
@@ -986,33 +980,17 @@
     }
 
     protected void engineInit(
-        int                 opmode,
-        Key                 key,
+        int opmode,
+        Key key,
         AlgorithmParameters params,
-        SecureRandom        random) 
-    throws InvalidKeyException, InvalidAlgorithmParameterException
+        SecureRandom random)
+        throws InvalidKeyException, InvalidAlgorithmParameterException
     {
-        AlgorithmParameterSpec  paramSpec = null;
+        AlgorithmParameterSpec paramSpec = null;
 
         if (params != null)
         {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                if (availableSpecs[i] == null)
-                {
-                    continue;
-                }
-
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    // try again if possible
-                }
-            }
+            paramSpec = SpecUtil.extractSpec(params, availableSpecs);
 
             if (paramSpec == null)
             {
@@ -1021,14 +999,14 @@
         }
 
         engineInit(opmode, key, paramSpec, random);
-        
+
         engineParams = params;
     }
 
     protected void engineInit(
-        int                 opmode,
-        Key                 key,
-        SecureRandom        random) 
+        int opmode,
+        Key key,
+        SecureRandom random)
         throws InvalidKeyException
     {
         try
@@ -1081,32 +1059,32 @@
     }
 
     protected byte[] engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
+        byte[] input,
+        int inputOffset,
+        int inputLen)
     {
-        int     length = cipher.getUpdateOutputSize(inputLen);
+        int length = cipher.getUpdateOutputSize(inputLen);
 
         if (length > 0)
         {
-                byte[]  out = new byte[length];
+            byte[] out = new byte[length];
 
-                int len = cipher.processBytes(input, inputOffset, inputLen, out, 0);
+            int len = cipher.processBytes(input, inputOffset, inputLen, out, 0);
 
-                if (len == 0)
-                {
-                    return null;
-                }
-                else if (len != out.length)
-                {
-                    byte[]  tmp = new byte[len];
+            if (len == 0)
+            {
+                return null;
+            }
+            else if (len != out.length)
+            {
+                byte[] tmp = new byte[len];
 
-                    System.arraycopy(out, 0, tmp, 0, len);
+                System.arraycopy(out, 0, tmp, 0, len);
 
-                    return tmp;
-                }
+                return tmp;
+            }
 
-                return out;
+            return out;
         }
 
         cipher.processBytes(input, inputOffset, inputLen, null, 0);
@@ -1115,11 +1093,11 @@
     }
 
     protected int engineUpdate(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset)
+        byte[] input,
+        int inputOffset,
+        int inputLen,
+        byte[] output,
+        int outputOffset)
         throws ShortBufferException
     {
         if (outputOffset + cipher.getUpdateOutputSize(inputLen) > output.length)
@@ -1139,13 +1117,13 @@
     }
 
     protected byte[] engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen) 
+        byte[] input,
+        int inputOffset,
+        int inputLen)
         throws IllegalBlockSizeException, BadPaddingException
     {
-        int     len = 0;
-        byte[]  tmp = new byte[engineGetOutputSize(inputLen)];
+        int len = 0;
+        byte[] tmp = new byte[engineGetOutputSize(inputLen)];
 
         if (inputLen != 0)
         {
@@ -1166,7 +1144,12 @@
             return tmp;
         }
 
-        byte[]  out = new byte[len];
+        if (len > tmp.length)
+        {
+            throw new IllegalBlockSizeException("internal buffer overflow");
+        }
+
+        byte[] out = new byte[len];
 
         System.arraycopy(tmp, 0, out, 0, len);
 
@@ -1174,14 +1157,14 @@
     }
 
     protected int engineDoFinal(
-        byte[]  input,
-        int     inputOffset,
-        int     inputLen,
-        byte[]  output,
-        int     outputOffset)
+        byte[] input,
+        int inputOffset,
+        int inputLen,
+        byte[] output,
+        int outputOffset)
         throws IllegalBlockSizeException, BadPaddingException, ShortBufferException
     {
-        int     len = 0;
+        int len = 0;
 
         if (outputOffset + engineGetOutputSize(inputLen) > output.length)
         {
@@ -1301,17 +1284,20 @@
             throw new UnsupportedOperationException("AAD is not supported in the current mode.");
         }
 
-        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
+        public int processByte(byte in, byte[] out, int outOff)
+            throws DataLengthException
         {
             return cipher.processByte(in, out, outOff);
         }
 
-        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
+        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+            throws DataLengthException
         {
             return cipher.processBytes(in, inOff, len, out, outOff);
         }
 
-        public int doFinal(byte[] out, int outOff) throws IllegalStateException, BadPaddingException
+        public int doFinal(byte[] out, int outOff)
+            throws IllegalStateException, BadPaddingException
         {
             try
             {
@@ -1329,7 +1315,8 @@
     {
         private static final Constructor aeadBadTagConstructor;
 
-        static {
+        static
+        {
             Class aeadBadTagClass = ClassUtil.loadClass(BaseBlockCipher.class, "javax.crypto.AEADBadTagException");
             if (aeadBadTagClass != null)
             {
@@ -1406,17 +1393,20 @@
             cipher.processAADBytes(input, offset, length);
         }
 
-        public int processByte(byte in, byte[] out, int outOff) throws DataLengthException
+        public int processByte(byte in, byte[] out, int outOff)
+            throws DataLengthException
         {
             return cipher.processByte(in, out, outOff);
         }
 
-        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) throws DataLengthException
+        public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+            throws DataLengthException
         {
             return cipher.processBytes(in, inOff, len, out, outOff);
         }
 
-        public int doFinal(byte[] out, int outOff) throws IllegalStateException, BadPaddingException
+        public int doFinal(byte[] out, int outOff)
+            throws IllegalStateException, BadPaddingException
         {
             try
             {
@@ -1430,7 +1420,7 @@
                     try
                     {
                         aeadBadTag = (BadPaddingException)aeadBadTagConstructor
-                                .newInstance(new Object[]{e.getMessage()});
+                            .newInstance(new Object[]{e.getMessage()});
                     }
                     catch (Exception i)
                     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java
index e1ce811..c93b597 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseMac.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.jcajce.provider.symmetric.util;
 
-import java.lang.reflect.Method;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.Key;
@@ -59,7 +58,7 @@
 
     protected void engineInit(
         Key                     key,
-        AlgorithmParameterSpec  params)
+        final AlgorithmParameterSpec  params)
         throws InvalidKeyException, InvalidAlgorithmParameterException
     {
         CipherParameters        param;
@@ -168,7 +167,7 @@
             param = new KeyParameter(key.getEncoded());
         }
 
-        KeyParameter keyParam;
+        final KeyParameter keyParam;
         if (param instanceof ParametersWithIV)
         {
             keyParam = (KeyParameter)((ParametersWithIV)param).getParameters();
@@ -202,17 +201,7 @@
         }
         else if (gcmSpecClass != null && gcmSpecClass.isAssignableFrom(params.getClass()))
         {
-            try
-            {
-                Method tLen = gcmSpecClass.getDeclaredMethod("getTLen", new Class[0]);
-                Method iv= gcmSpecClass.getDeclaredMethod("getIV", new Class[0]);
-
-                param = new AEADParameters(keyParam, ((Integer)tLen.invoke(params, new Object[0])).intValue(), (byte[])iv.invoke(params, new Object[0]));
-            }
-            catch (Exception e)
-            {
-                throw new InvalidAlgorithmParameterException("Cannot process GCMParameterSpec.");
-            }
+            param = GcmSpecUtil.extractAeadParameters(keyParam, params);
         }
         else if (!(params instanceof PBEParameterSpec))
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java
index b45f766..de89a53 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseStreamCipher.java
@@ -322,18 +322,7 @@
 
         if (params != null)
         {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    continue;
-                }
-            }
+            paramSpec = SpecUtil.extractSpec(params, availableSpecs);
 
             if (paramSpec == null)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java
index f76cd2f..080566f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/BaseWrapCipher.java
@@ -274,18 +274,7 @@
 
         if (params != null)
         {
-            for (int i = 0; i != availableSpecs.length; i++)
-            {
-                try
-                {
-                    paramSpec = params.getParameterSpec(availableSpecs[i]);
-                    break;
-                }
-                catch (Exception e)
-                {
-                    // try next spec
-                }
-            }
+            paramSpec = SpecUtil.extractSpec(params, availableSpecs);
 
             if (paramSpec == null)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil.java
new file mode 100644
index 0000000..6cf2020
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/GcmSpecUtil.java
@@ -0,0 +1,132 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.cms.GCMParameters;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.util.Integers;
+
+public class GcmSpecUtil
+{
+    static final Class gcmSpecClass = ClassUtil.loadClass(GcmSpecUtil.class, "javax.crypto.spec.GCMParameterSpec");
+
+    static final Method tLen;
+    static final Method iv;
+
+    static
+    {
+        if (gcmSpecClass != null)
+        {
+            tLen = extractMethod("getTLen");
+            iv = extractMethod("getIV");
+        }
+        else
+        {
+            tLen = null;
+            iv = null;
+        }
+    }
+
+    private static Method extractMethod(final String name)
+    {
+        try
+        {
+            return (Method)AccessController.doPrivileged(new PrivilegedExceptionAction()
+            {
+                public Object run()
+                    throws Exception
+                {
+                    return gcmSpecClass.getDeclaredMethod(name, new Class[0]);
+                }
+            });
+        }
+        catch (PrivilegedActionException e)
+        {
+            return null;
+        }
+    }
+
+    public static boolean gcmSpecExists()
+    {
+        return gcmSpecClass != null;
+    }
+
+    public static boolean isGcmSpec(AlgorithmParameterSpec paramSpec)
+    {
+        return gcmSpecClass != null && gcmSpecClass.isInstance(paramSpec);
+    }
+
+    public static boolean isGcmSpec(Class paramSpecClass)
+    {
+        return gcmSpecClass == paramSpecClass;
+    }
+
+    public static AlgorithmParameterSpec extractGcmSpec(ASN1Primitive spec)
+        throws InvalidParameterSpecException
+    {
+        try
+        {
+            GCMParameters gcmParams = GCMParameters.getInstance(spec);
+            Constructor constructor = gcmSpecClass.getConstructor(new Class[]{Integer.TYPE, byte[].class});
+
+            return (AlgorithmParameterSpec)constructor.newInstance(new Object[] { Integers.valueOf(gcmParams.getIcvLen() * 8), gcmParams.getNonce() });
+        }
+        catch (NoSuchMethodException e)
+        {
+            throw new InvalidParameterSpecException("No constructor found!");   // should never happen
+        }
+        catch (Exception e)
+        {
+            throw new InvalidParameterSpecException("Construction failed: " + e.getMessage());   // should never happen
+        }
+    }
+
+    static AEADParameters extractAeadParameters(final KeyParameter keyParam, final AlgorithmParameterSpec params)
+        throws InvalidAlgorithmParameterException
+    {
+        try
+        {
+            return (AEADParameters)AccessController.doPrivileged(new PrivilegedExceptionAction()
+            {
+                public Object run()
+                    throws Exception
+                {
+                    return new AEADParameters(keyParam, ((Integer)tLen.invoke(params, new Object[0])).intValue(), (byte[])iv.invoke(params, new Object[0]));
+                }
+            });
+        }
+        catch (Exception e)
+        {
+            throw new InvalidAlgorithmParameterException("Cannot process GCMParameterSpec.");
+        }
+    }
+
+    public static GCMParameters extractGcmParameters(final AlgorithmParameterSpec paramSpec)
+        throws InvalidParameterSpecException
+    {
+        try
+        {
+            return (GCMParameters)AccessController.doPrivileged(new PrivilegedExceptionAction()
+            {
+                public Object run()
+                    throws Exception
+                {
+                    return new GCMParameters((byte[])iv.invoke(paramSpec, new Object[0]), ((Integer)tLen.invoke(paramSpec, new Object[0])).intValue() / 8);
+                }
+            });
+        }
+        catch (Exception e)
+        {
+            throw new InvalidParameterSpecException("Cannot process GCMParameterSpec");
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/SpecUtil.java b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/SpecUtil.java
new file mode 100644
index 0000000..f20c8c0
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/provider/symmetric/util/SpecUtil.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.jcajce.provider.symmetric.util;
+
+import java.security.AlgorithmParameters;
+import java.security.spec.AlgorithmParameterSpec;
+
+class SpecUtil
+{
+    static AlgorithmParameterSpec extractSpec(AlgorithmParameters params, Class[] availableSpecs)
+    {
+        try
+        {
+            return params.getParameterSpec(AlgorithmParameterSpec.class);
+        }
+        catch (Exception e)
+        {
+            for (int i = 0; i != availableSpecs.length; i++)
+            {
+                if (availableSpecs[i] == null)
+                {
+                    continue;
+                }
+
+                try
+                {
+                    return params.getParameterSpec(availableSpecs[i]);
+                }
+                catch (Exception ex)
+                {
+                    // try again if possible
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/spec/CompositeAlgorithmSpec.java b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/CompositeAlgorithmSpec.java
new file mode 100644
index 0000000..ee8f43c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/CompositeAlgorithmSpec.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CompositeAlgorithmSpec
+    implements AlgorithmParameterSpec
+{
+    public static class Builder
+    {
+        private List<String> algorithmNames = new ArrayList<String>();
+        private List<AlgorithmParameterSpec> parameterSpecs = new ArrayList<AlgorithmParameterSpec>();
+
+        public Builder()
+        {
+        }
+
+        public Builder add(String algorithmName)
+        {
+            algorithmNames.add(algorithmName);
+            parameterSpecs.add(null);
+
+            return this;
+        }
+
+        public Builder add(String algorithmName, AlgorithmParameterSpec parameterSpec)
+        {
+            algorithmNames.add(algorithmName);
+            parameterSpecs.add(parameterSpec);
+
+            return this;
+        }
+
+        public CompositeAlgorithmSpec build()
+        {
+            if (algorithmNames.isEmpty())
+            {
+                throw new IllegalStateException("cannot call build with no algorithm names added");
+            }
+
+            return new CompositeAlgorithmSpec(this);
+        }
+    }
+
+    private final List<String> algorithmNames;
+    private final List<AlgorithmParameterSpec> parameterSpecs;
+
+    public CompositeAlgorithmSpec(Builder builder)
+    {
+         this.algorithmNames = Collections.unmodifiableList(new ArrayList<String>(builder.algorithmNames));
+         this.parameterSpecs = Collections.unmodifiableList(new ArrayList<AlgorithmParameterSpec>(builder.parameterSpecs));
+    }
+
+    public List<String> getAlgorithmNames()
+    {
+        return algorithmNames;
+    }
+
+    public List<AlgorithmParameterSpec> getParameterSpecs()
+    {
+        return parameterSpecs;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPrivateKeySpec.java b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPrivateKeySpec.java
new file mode 100644
index 0000000..f72dc3c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPrivateKeySpec.java
@@ -0,0 +1,37 @@
+package org.bouncycastle.jcajce.spec;
+
+import java.math.BigInteger;
+
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+/**
+ * A DHPrivateKeySpec that also carries a set of DH domain parameters.
+ */
+public class DHExtendedPrivateKeySpec
+    extends DHPrivateKeySpec
+{
+    private final DHParameterSpec params;
+
+    /**
+     * Base constructor.
+     *
+     * @param x the private value.
+     * @param params the domain parameter set.
+     */
+    public DHExtendedPrivateKeySpec(BigInteger x, DHParameterSpec params)
+    {
+        super(x, params.getP(), params.getG());
+        this.params = params;
+    }
+
+    /**
+     * Return the domain parameters associated with this key spec.
+     *
+     * @return the Diffie-Hellman domain parameters.
+     */
+    public DHParameterSpec getParams()
+    {
+        return params;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPublicKeySpec.java b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPublicKeySpec.java
new file mode 100644
index 0000000..86429a6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/spec/DHExtendedPublicKeySpec.java
@@ -0,0 +1,37 @@
+package org.bouncycastle.jcajce.spec;
+
+import java.math.BigInteger;
+
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+/**
+ * A DHPublicKeySpec that also carries a set of DH domain parameters.
+ */
+public class DHExtendedPublicKeySpec
+    extends DHPublicKeySpec
+{
+    private final DHParameterSpec params;
+
+    /**
+     * Base constructor.
+     *
+     * @param y the public value.
+     * @param params the domain parameter set.
+     */
+    public DHExtendedPublicKeySpec(BigInteger y, DHParameterSpec params)
+    {
+        super(y, params.getP(), params.getG());
+        this.params = params;
+    }
+
+    /**
+     * Return the domain parameters associated with this key spec.
+     *
+     * @return the Diffie-Hellman domain parameters.
+     */
+    public DHParameterSpec getParams()
+    {
+        return params;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/util/BCJcaJceHelper.java b/bcprov/src/main/java/org/bouncycastle/jcajce/util/BCJcaJceHelper.java
index 892aa00..6c38458 100644
--- a/bcprov/src/main/java/org/bouncycastle/jcajce/util/BCJcaJceHelper.java
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/util/BCJcaJceHelper.java
@@ -15,9 +15,12 @@
 
     private static synchronized Provider getBouncyCastleProvider()
     {
-        if (Security.getProvider("BC") != null)
+        final Provider system = Security.getProvider("BC");
+        // Avoid using the old, deprecated system BC provider on Android.
+        // See: https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html
+        if (system instanceof BouncyCastleProvider)
         {
-            return Security.getProvider("BC");
+            return system;
         }
         else if (bcProvider != null)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/jcajce/util/ECKeyUtil.java b/bcprov/src/main/java/org/bouncycastle/jcajce/util/ECKeyUtil.java
new file mode 100644
index 0000000..a37e4e1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jcajce/util/ECKeyUtil.java
@@ -0,0 +1,106 @@
+package org.bouncycastle.jcajce.util;
+
+import java.io.IOException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.ECNamedCurveTable;
+import org.bouncycastle.asn1.x9.X962Parameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.asn1.x9.X9ECPoint;
+import org.bouncycastle.crypto.ec.CustomNamedCurves;
+
+/**
+ * Utility class for EC Keys.
+ */
+public class ECKeyUtil
+{
+    /**
+     * Convert an ECPublicKey into an ECPublicKey which always encodes
+     * with point compression.
+     *
+     * @param ecPublicKey the originating public key.
+     * @return a wrapped version of ecPublicKey which uses point compression.
+     */
+    public static ECPublicKey createKeyWithCompression(ECPublicKey ecPublicKey)
+    {
+        return new ECPublicKeyWithCompression(ecPublicKey);
+    }
+
+    private static class ECPublicKeyWithCompression
+        implements ECPublicKey
+    {
+        private final ECPublicKey ecPublicKey;
+
+        public ECPublicKeyWithCompression(ECPublicKey ecPublicKey)
+        {
+            this.ecPublicKey = ecPublicKey;
+        }
+
+        public ECPoint getW()
+        {
+            return ecPublicKey.getW();
+        }
+
+        public String getAlgorithm()
+        {
+            return ecPublicKey.getAlgorithm();
+        }
+
+        public String getFormat()
+        {
+            return ecPublicKey.getFormat();
+        }
+
+        public byte[] getEncoded()
+        {
+            SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(ecPublicKey.getEncoded());
+
+            X962Parameters params = X962Parameters.getInstance(publicKeyInfo.getAlgorithm().getParameters());
+
+            org.bouncycastle.math.ec.ECCurve curve;
+
+            if (params.isNamedCurve())
+            {
+                ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)params.getParameters();
+
+                X9ECParameters x9 = CustomNamedCurves.getByOID(oid);
+                if (x9 == null)
+                {
+                    x9 = ECNamedCurveTable.getByOID(oid);
+                }
+                curve = x9.getCurve();
+            }
+            else if (params.isImplicitlyCA())
+            {
+                throw new IllegalStateException("unable to identify implictlyCA");
+            }
+            else
+            {
+                X9ECParameters x9 = X9ECParameters.getInstance(params.getParameters());
+                curve = x9.getCurve();
+            }
+
+            org.bouncycastle.math.ec.ECPoint p = curve.decodePoint(publicKeyInfo.getPublicKeyData().getOctets());
+            ASN1OctetString pEnc = ASN1OctetString.getInstance(new X9ECPoint(p,true).toASN1Primitive());
+
+            try
+            {
+                return new SubjectPublicKeyInfo(publicKeyInfo.getAlgorithm(), pEnc.getOctets()).getEncoded();
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException("unable to encode EC public key: " + e.getMessage());
+            }
+        }
+
+        public ECParameterSpec getParams()
+        {
+            return ecPublicKey.getParams();
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java b/bcprov/src/main/java/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
index 7843e0a..a70aa3b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/ECGOST3410NamedCurveTable.java
@@ -4,7 +4,7 @@
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.cryptopro.ECGOST3410NamedCurves;
-import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.asn1.x9.X9ECParameters;
 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
 
 /**
@@ -22,12 +22,12 @@
     public static ECNamedCurveParameterSpec getParameterSpec(
         String  name)
     {
-        ECDomainParameters  ecP = ECGOST3410NamedCurves.getByName(name);
+        X9ECParameters  ecP = ECGOST3410NamedCurves.getByNameX9(name);
         if (ecP == null)
         {
             try
             {
-                ecP = ECGOST3410NamedCurves.getByOID(new ASN1ObjectIdentifier(name));
+                ecP = ECGOST3410NamedCurves.getByOIDX9(new ASN1ObjectIdentifier(name));
             }
             catch (IllegalArgumentException e)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/PKCS10CertificationRequest.java b/bcprov/src/main/java/org/bouncycastle/jce/PKCS10CertificationRequest.java
index 3dbd6ec..f6e2676 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/PKCS10CertificationRequest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/PKCS10CertificationRequest.java
@@ -166,6 +166,7 @@
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA384);
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA512);
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
+        noParams.add(OIWObjectIdentifiers.dsaWithSHA1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/exception/ExtCertPathValidatorException.java b/bcprov/src/main/java/org/bouncycastle/jce/exception/ExtCertPathValidatorException.java
index e36848f..2647051 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/exception/ExtCertPathValidatorException.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/exception/ExtCertPathValidatorException.java
@@ -10,6 +10,11 @@
 
     private Throwable cause;
 
+    public ExtCertPathValidatorException(String message)
+    {
+        super(message);
+    }
+
     public ExtCertPathValidatorException(String message, Throwable cause)
     {
         super(message);
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/BouncyCastleProvider.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/BouncyCastleProvider.java
index 95c2ec7..11c177a 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/BouncyCastleProvider.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/BouncyCastleProvider.java
@@ -11,6 +11,8 @@
 import java.util.Map;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
@@ -19,6 +21,7 @@
 import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;
 import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
 import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
+import org.bouncycastle.pqc.jcajce.provider.lms.LMSKeyFactorySpi;
 import org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceCCA2KeyFactorySpi;
 import org.bouncycastle.pqc.jcajce.provider.mceliece.McElieceKeyFactorySpi;
 import org.bouncycastle.pqc.jcajce.provider.newhope.NHKeyFactorySpi;
@@ -55,7 +58,7 @@
 public final class BouncyCastleProvider extends Provider
     implements ConfigurableProvider
 {
-    private static String info = "BouncyCastle Security Provider v1.64";
+    private static String info = "BouncyCastle Security Provider v1.68";
 
     public static final String PROVIDER_NAME = "BC";
 
@@ -63,6 +66,8 @@
 
     private static final Map keyInfoConverters = new HashMap();
 
+    private static final Class revChkClass = ClassUtil.loadClass(BouncyCastleProvider.class, "java.security.cert.PKIXRevocationChecker");
+
     /*
      * Configurable symmetric ciphers
      */
@@ -75,7 +80,7 @@
 
     private static final String[] SYMMETRIC_MACS =
     {
-        "SipHash", "Poly1305"
+        "SipHash", "SipHash128", "Poly1305"
     };
 
     private static final String[] SYMMETRIC_CIPHERS =
@@ -95,7 +100,7 @@
     // later ones configure it.
     private static final String[] ASYMMETRIC_GENERIC =
     {
-        "X509", "IES"
+        "X509", "IES", "COMPOSITE"
     };
 
     private static final String[] ASYMMETRIC_CIPHERS =
@@ -139,7 +144,7 @@
      */
     public BouncyCastleProvider()
     {
-        super(PROVIDER_NAME, 1.64, info);
+        super(PROVIDER_NAME, 1.68, info);
 
         AccessController.doPrivileged(new PrivilegedAction()
         {
@@ -170,6 +175,7 @@
         loadAlgorithms(SECURE_RANDOM_PACKAGE, SECURE_RANDOMS);
 
         loadPQCKeys();  // so we can handle certificates containing them.
+
         //
         // X509Store
         //
@@ -202,12 +208,24 @@
         put("Cipher.OLDPBEWITHSHAANDTWOFISH-CBC", "org.bouncycastle.jce.provider.BrokenJCEBlockCipher$OldPBEWithSHAAndTwofish");
 
         // Certification Path API
-        put("CertPathValidator.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathValidatorSpi");
-        put("CertPathBuilder.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathBuilderSpi");
-        put("CertPathValidator.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
-        put("CertPathBuilder.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
-        put("CertPathValidator.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
-        put("CertPathBuilder.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
+        if (revChkClass != null)
+        {
+            put("CertPathValidator.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathValidatorSpi");
+            put("CertPathBuilder.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathBuilderSpi");
+            put("CertPathValidator.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8");
+            put("CertPathBuilder.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8");
+            put("CertPathValidator.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8");
+            put("CertPathBuilder.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8");
+        }
+        else
+        {
+            put("CertPathValidator.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathValidatorSpi");
+            put("CertPathBuilder.RFC3281", "org.bouncycastle.jce.provider.PKIXAttrCertPathBuilderSpi");
+            put("CertPathValidator.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
+            put("CertPathBuilder.RFC3280", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
+            put("CertPathValidator.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi");
+            put("CertPathBuilder.PKIX", "org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi");
+        }
         put("CertStore.Collection", "org.bouncycastle.jce.provider.CertStoreCollectionSpi");
         put("CertStore.LDAP", "org.bouncycastle.jce.provider.X509LDAPCertStoreSpi");
         put("CertStore.Multi", "org.bouncycastle.jce.provider.MultiCertStoreSpi");
@@ -240,12 +258,15 @@
         addKeyInfoConverter(PQCObjectIdentifiers.sphincs256, new Sphincs256KeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.newHope, new NHKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.xmss, new XMSSKeyFactorySpi());
+        addKeyInfoConverter(IsaraObjectIdentifiers.id_alg_xmss, new XMSSKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.xmss_mt, new XMSSMTKeyFactorySpi());
+        addKeyInfoConverter(IsaraObjectIdentifiers.id_alg_xmssmt, new XMSSMTKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.mcEliece, new McElieceKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.mcElieceCca2, new McElieceCCA2KeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.rainbow, new RainbowKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.qTESLA_p_I, new QTESLAKeyFactorySpi());
         addKeyInfoConverter(PQCObjectIdentifiers.qTESLA_p_III, new QTESLAKeyFactorySpi());
+        addKeyInfoConverter(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, new LMSKeyFactorySpi());
     }
 
     public void setParameter(String parameterName, Object parameter)
@@ -285,6 +306,11 @@
         }
     }
 
+    public AsymmetricKeyInfoConverter getKeyInfoConverter(ASN1ObjectIdentifier oid)
+    {
+        return (AsymmetricKeyInfoConverter)keyInfoConverters.get(oid);
+    }
+
     public void addAttributes(String key, Map<String, String> attributeMap)
     {
         for (Iterator it = attributeMap.keySet().iterator(); it.hasNext();)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/BrokenPBE.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/BrokenPBE.java
index a173625..509e76f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/BrokenPBE.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/BrokenPBE.java
@@ -24,7 +24,7 @@
  * use it (it won't be staying around).
  * <p>
  * The document this implementation is based on can be found at
- * <a href=http://www.rsasecurity.com/rsalabs/pkcs/pkcs-12/index.html>
+ * <a href=https://www.rsasecurity.com/rsalabs/pkcs/pkcs-12/index.html>
  * RSA's PKCS12 Page</a>
  */
 class OldPKCS12ParametersGenerator
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
index ad17cc2..3054aa1 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/CertPathValidatorUtilities.java
@@ -3,15 +3,18 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.net.URI;
 import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.PublicKey;
 import java.security.cert.CRLException;
 import java.security.cert.CertPath;
+import java.security.cert.CertPathBuilderException;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertStore;
 import java.security.cert.CertStoreException;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.PolicyQualifierInfo;
 import java.security.cert.TrustAnchor;
@@ -41,13 +44,13 @@
 import org.bouncycastle.asn1.ASN1Encodable;
 import org.bouncycastle.asn1.ASN1Enumerated;
 import org.bouncycastle.asn1.ASN1GeneralizedTime;
-import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.isismtt.ISISMTTObjectIdentifiers;
@@ -66,11 +69,15 @@
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.jcajce.PKIXCRLStore;
 import org.bouncycastle.jcajce.PKIXCRLStoreSelector;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
 import org.bouncycastle.jcajce.PKIXCertStore;
 import org.bouncycastle.jcajce.PKIXCertStoreSelector;
+import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
 import org.bouncycastle.jcajce.PKIXExtendedParameters;
 import org.bouncycastle.jcajce.util.JcaJceHelper;
+import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
 import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Properties;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.Store;
 import org.bouncycastle.util.StoreException;
@@ -78,8 +85,6 @@
 
 class CertPathValidatorUtilities
 {
-    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
-
     protected static final String CERTIFICATE_POLICIES = Extension.certificatePolicies.getId();
     protected static final String BASIC_CONSTRAINTS = Extension.basicConstraints.getId();
     protected static final String POLICY_MAPPINGS = Extension.policyMappings.getId();
@@ -117,6 +122,36 @@
         "privilegeWithdrawn",
         "aACompromise"};
 
+    static Collection findTargets(PKIXExtendedBuilderParameters paramsPKIX) throws CertPathBuilderException
+    {
+        PKIXExtendedParameters baseParams = paramsPKIX.getBaseParameters();
+        PKIXCertStoreSelector certSelect = baseParams.getTargetConstraints();
+        LinkedHashSet targets = new LinkedHashSet();
+
+        try
+        {
+            CertPathValidatorUtilities.findCertificates(targets, certSelect, baseParams.getCertificateStores());
+            CertPathValidatorUtilities.findCertificates(targets, certSelect, baseParams.getCertStores());
+        }
+        catch (AnnotatedException e)
+        {
+            throw new ExtCertPathBuilderException("Error finding target certificate.", e);
+        }
+
+        if (!targets.isEmpty())
+        {
+            return targets;
+        }
+
+        Certificate target = certSelect.getCertificate();
+        if (null == target)
+        {
+            throw new CertPathBuilderException("No certificate found matching targetConstraints.");
+        }
+
+        return Collections.singleton(target);
+    }
+
     /**
      * Search the given Set of TrustAnchor's for one that is the
      * issuer of the given X509 certificate. Uses the default provider
@@ -261,43 +296,35 @@
     {
         // if in the IssuerAltName extension an URI
         // is given, add an additional X.509 store
-        if (issuerAlternativeName != null)
-        {
-            GeneralNames issuerAltName = GeneralNames.getInstance(ASN1OctetString.getInstance(issuerAlternativeName).getOctets());
-
-            GeneralName[] names = issuerAltName.getNames();
-            List<PKIXCertStore>  stores = new ArrayList<PKIXCertStore>();
-
-            for (int i = 0; i != names.length; i++)
-            {
-                GeneralName altName = names[i];
-
-                PKIXCertStore altStore = altNameCertStoreMap.get(altName);
-
-                if (altStore != null)
-                {
-                    stores.add(altStore);
-                }
-            }
-
-            return stores;
-        }
-        else
+        if (issuerAlternativeName == null)
         {
             return Collections.EMPTY_LIST;
         }
-    }
 
-    protected static Date getValidDate(PKIXExtendedParameters paramsPKIX)
-    {
-        Date validDate = paramsPKIX.getDate();
+        GeneralNames issuerAltName = GeneralNames.getInstance(ASN1OctetString.getInstance(issuerAlternativeName).getOctets());
 
-        if (validDate == null)
+        GeneralName[] names = issuerAltName.getNames();
+        List<PKIXCertStore>  stores = new ArrayList<PKIXCertStore>();
+
+        for (int i = 0; i != names.length; i++)
         {
-            validDate = new Date();
+            GeneralName altName = names[i];
+
+            PKIXCertStore altStore = altNameCertStoreMap.get(altName);
+            if (altStore != null)
+            {
+                stores.add(altStore);
+            }
         }
 
-        return validDate;
+        return stores;
+    }
+
+    protected static Date getValidityDate(PKIXExtendedParameters paramsPKIX, Date currentDate)
+    {
+        Date validityDate = paramsPKIX.getValidityDate();
+
+        return null == validityDate ? currentDate : validityDate;
     }
 
     protected static boolean isSelfIssued(X509Certificate cert)
@@ -305,7 +332,6 @@
         return cert.getSubjectDN().equals(cert.getIssuerDN());
     }
 
-
     /**
      * Extract the value of the given extension, if it exists.
      *
@@ -313,29 +339,19 @@
      * @param oid The object identifier to obtain.
      * @throws AnnotatedException if the extension cannot be read.
      */
-    protected static ASN1Primitive getExtensionValue(
-        java.security.cert.X509Extension ext,
-        String oid)
+    protected static ASN1Primitive getExtensionValue(java.security.cert.X509Extension ext, String oid)
         throws AnnotatedException
     {
         byte[] bytes = ext.getExtensionValue(oid);
-        if (bytes == null)
-        {
-            return null;
-        }
 
-        return getObject(oid, bytes);
+        return null == bytes ? null : getObject(oid, bytes);
     }
 
-    private static ASN1Primitive getObject(
-        String oid,
-        byte[] ext)
-        throws AnnotatedException
+    private static ASN1Primitive getObject(String oid, byte[] ext) throws AnnotatedException
     {
         try
         {
-            ASN1InputStream aIn = new ASN1InputStream(ext);
-            ASN1OctetString octs = ASN1OctetString.getInstance(aIn.readObject());
+            ASN1OctetString octs = ASN1OctetString.getInstance(ext);
 
             return ASN1Primitive.fromByteArray(octs.getOctets());
         }
@@ -345,17 +361,11 @@
         }
     }
 
-    protected static AlgorithmIdentifier getAlgorithmIdentifier(
-        PublicKey key)
-        throws CertPathValidatorException
+    protected static AlgorithmIdentifier getAlgorithmIdentifier(PublicKey key) throws CertPathValidatorException
     {
         try
         {
-            ASN1InputStream aIn = new ASN1InputStream(key.getEncoded());
-
-            SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(aIn.readObject());
-
-            return info.getAlgorithm();
+            return SubjectPublicKeyInfo.getInstance(key.getEncoded()).getAlgorithm();
         }
         catch (Exception e)
         {
@@ -649,27 +659,26 @@
     }
 
     /**
-     * Return a Collection of all certificates or attribute certificates found
-     * in the X509Store's that are matching the certSelect criteriums.
+     * Return a Collection of all certificates or attribute certificates found in the X509Store's
+     * that are matching the certSelect criteriums.
      *
-     * @param certSelect a {@link Selector} object that will be used to select
-     *                   the certificates
-     * @param certStores a List containing only {@link Store} objects. These
-     *                   are used to search for certificates.
-     * @return a Collection of all found {@link X509Certificate}
-     *         May be empty but never <code>null</code>.
+     * @param certs
+     *            a {@link LinkedHashSet} to which the certificates will be added.
+     * @param certSelect
+     *            a {@link Selector} object that will be used to select the certificates
+     * @param certStores
+     *            a List containing only {@link Store} objects. These are used to search for
+     *            certificates.
+     * @return a Collection of all found {@link X509Certificate} May be empty but never
+     *         <code>null</code>.
      */
-    protected static Collection findCertificates(PKIXCertStoreSelector certSelect,
-                                                 List certStores)
+    protected static void findCertificates(LinkedHashSet certs, PKIXCertStoreSelector certSelect, List certStores)
         throws AnnotatedException
     {
-        Set certs = new LinkedHashSet();
         Iterator iter = certStores.iterator();
-
         while (iter.hasNext())
         {
             Object obj = iter.next();
-
             if (obj instanceof Store)
             {
                 Store certStore = (Store)obj;
@@ -679,75 +688,111 @@
                 }
                 catch (StoreException e)
                 {
-                    throw new AnnotatedException(
-                            "Problem while picking certificates from X.509 store.", e);
+                    throw new AnnotatedException("Problem while picking certificates from X.509 store.", e);
                 }
             }
             else
             {
                 CertStore certStore = (CertStore)obj;
-
                 try
                 {
                     certs.addAll(PKIXCertStoreSelector.getCertificates(certSelect, certStore));
                 }
                 catch (CertStoreException e)
                 {
-                    throw new AnnotatedException(
-                        "Problem while picking certificates from certificate store.",
-                        e);
+                    throw new AnnotatedException("Problem while picking certificates from certificate store.", e);
                 }
             }
         }
-        return certs;
     }
 
-    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(CRLDistPoint crldp, Map<GeneralName, PKIXCRLStore> namedCRLStoreMap)
+    static List<PKIXCRLStore> getAdditionalStoresFromCRLDistributionPoint(
+        CRLDistPoint crldp, Map<GeneralName, PKIXCRLStore> namedCRLStoreMap, Date validDate, JcaJceHelper helper)
         throws AnnotatedException
     {
-        if (crldp != null)
+        if (null == crldp)
         {
-            DistributionPoint dps[] = null;
+            return Collections.EMPTY_LIST;
+        }
+
+        DistributionPoint dps[];
+        try
+        {
+            dps = crldp.getDistributionPoints();
+        }
+        catch (Exception e)
+        {
+            throw new AnnotatedException("Distribution points could not be read.", e);
+        }
+
+        List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
+
+        for (int i = 0; i < dps.length; i++)
+        {
+            DistributionPointName dpn = dps[i].getDistributionPoint();
+            // look for URIs in fullName
+            if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
+            {
+                GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
+
+                for (int j = 0; j < genNames.length; j++)
+                {
+                    PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
+                    if (store != null)
+                    {
+                        stores.add(store);
+                    }
+                }
+            }
+        }
+
+        // if the named CRL store is empty, and we're told to check with CRLDP
+        if (stores.isEmpty() && Properties.isOverrideSet("org.bouncycastle.x509.enableCRLDP"))
+        {
+            CertificateFactory certFact;
             try
             {
-                dps = crldp.getDistributionPoints();
+                certFact = helper.createCertificateFactory("X.509");
             }
             catch (Exception e)
             {
-                throw new AnnotatedException(
-                    "Distribution points could not be read.", e);
+                throw new AnnotatedException("cannot create certificate factory: " + e.getMessage(), e);
             }
-            List<PKIXCRLStore> stores = new ArrayList<PKIXCRLStore>();
 
             for (int i = 0; i < dps.length; i++)
             {
                 DistributionPointName dpn = dps[i].getDistributionPoint();
                 // look for URIs in fullName
-                if (dpn != null)
+                if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
                 {
-                    if (dpn.getType() == DistributionPointName.FULL_NAME)
-                    {
-                        GeneralName[] genNames = GeneralNames.getInstance(
-                            dpn.getName()).getNames();
+                    GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
 
-                        for (int j = 0; j < genNames.length; j++)
+                    for (int j = 0; j < genNames.length; j++)
+                    {
+                        GeneralName name = genNames[i];
+                        if (name.getTagNo() == GeneralName.uniformResourceIdentifier)
                         {
-                            PKIXCRLStore store = namedCRLStoreMap.get(genNames[j]);
-                            if (store != null)
+                            try
                             {
-                                stores.add(store);
+                                URI distributionPoint = new URI(((ASN1String)name.getName()).getString());
+                                PKIXCRLStore store = CrlCache.getCrl(certFact, validDate, distributionPoint);
+                                if (store != null)
+                                {
+                                    stores.add(store);
+                                }
+                                break;
+                            }
+                            catch (Exception e)
+                            {
+                                // ignore...  TODO: maybe log
                             }
                         }
                     }
                 }
             }
+        }
 
-            return stores;
-        }
-        else
-        {
-            return Collections.EMPTY_LIST;
-        }
+        return stores;
     }
 
     /**
@@ -784,14 +829,12 @@
                 {
                     try
                     {
-                        issuers.add(X500Name.getInstance(genNames[j].getName()
-                            .toASN1Primitive().getEncoded()));
+                        issuers.add(X500Name.getInstance(genNames[j].getName().toASN1Primitive().getEncoded()));
                     }
                     catch (IOException e)
                     {
                         throw new AnnotatedException(
-                            "CRL issuer information from distribution point cannot be decoded.",
-                            e);
+                            "CRL issuer information from distribution point cannot be decoded.", e);
                     }
                 }
             }
@@ -872,8 +915,7 @@
         }
     }
 
-    private static BigInteger getSerialNumber(
-        Object cert)
+    private static BigInteger getSerialNumber(Object cert)
     {
         return ((X509Certificate)cert).getSerialNumber();
     }
@@ -885,8 +927,6 @@
         CertStatus certStatus)
         throws AnnotatedException
     {
-        X509CRLEntry crl_entry = null;
-
         boolean isIndirect;
         try
         {
@@ -897,6 +937,7 @@
             throw new AnnotatedException("Failed check for indirect CRL.", exception);
         }
 
+        X509CRLEntry crl_entry;
         if (isIndirect)
         {
             crl_entry = crl.getRevokedCertificate(getSerialNumber(cert));
@@ -918,12 +959,12 @@
                 certIssuer = PrincipalUtils.getX500Name(certificateIssuer);
             }
 
-            if (! PrincipalUtils.getEncodedIssuerPrincipal(cert).equals(certIssuer))
+            if (!PrincipalUtils.getEncodedIssuerPrincipal(cert).equals(certIssuer))
             {
                 return;
             }
         }
-        else if (! PrincipalUtils.getEncodedIssuerPrincipal(cert).equals(PrincipalUtils.getIssuerPrincipal(crl)))
+        else if (!PrincipalUtils.getEncodedIssuerPrincipal(cert).equals(PrincipalUtils.getIssuerPrincipal(crl)))
         {
             return;  // not for our issuer, ignore
         }
@@ -942,22 +983,17 @@
         {
             if (crl_entry.hasUnsupportedCriticalExtension())
             {
-                throw new AnnotatedException(
-                      "CRL entry has unsupported critical extensions.");
+                throw new AnnotatedException("CRL entry has unsupported critical extensions.");
             }
 
             try
             {
                 reasonCode = ASN1Enumerated
-                    .getInstance(CertPathValidatorUtilities
-                        .getExtensionValue(crl_entry,
-                            Extension.reasonCode.getId()));
+                    .getInstance(CertPathValidatorUtilities.getExtensionValue(crl_entry, Extension.reasonCode.getId()));
             }
             catch (Exception e)
             {
-                throw new AnnotatedException(
-                    "Reason code CRL entry extension could not be decoded.",
-                    e);
+                throw new AnnotatedException("Reason code CRL entry extension could not be decoded.", e);
             }
         }
 
@@ -988,7 +1024,10 @@
      * CRLs.
      */
     protected static Set getDeltaCRLs(Date validityDate,
-                                      X509CRL completeCRL, List<CertStore> certStores, List<PKIXCRLStore> pkixCrlStores)
+                                      X509CRL completeCRL,
+                                      List<CertStore> certStores,
+                                      List<PKIXCRLStore> pkixCrlStores,
+                                      JcaJceHelper helper)
         throws AnnotatedException
     {
         X509CRLSelector baseDeltaSelect = new X509CRLSelector();
@@ -1002,13 +1041,10 @@
             throw new AnnotatedException("Cannot extract issuer from CRL.", e);
         }
 
-
-
         BigInteger completeCRLNumber = null;
         try
         {
-            ASN1Primitive derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL,
-                CRL_NUMBER);
+            ASN1Primitive derObject = CertPathValidatorUtilities.getExtensionValue(completeCRL, CRL_NUMBER);
             if (derObject != null)
             {
                 completeCRLNumber = ASN1Integer.getInstance(derObject).getPositiveValue();
@@ -1021,22 +1057,19 @@
         }
 
         // 5.2.4 (b)
-        byte[] idp = null;
+        byte[] idp;
         try
         {
             idp = completeCRL.getExtensionValue(ISSUING_DISTRIBUTION_POINT);
         }
         catch (Exception e)
         {
-            throw new AnnotatedException(
-                "Issuing distribution point extension value could not be read.",
-                e);
+            throw new AnnotatedException("Issuing distribution point extension value could not be read.", e);
         }
 
         // 5.2.4 (d)
 
-        baseDeltaSelect.setMinCRLNumber(completeCRLNumber == null ? null : completeCRLNumber
-            .add(BigInteger.valueOf(1)));
+        baseDeltaSelect.setMinCRLNumber(completeCRLNumber == null ? null : completeCRLNumber.add(BigInteger.valueOf(1)));
 
         PKIXCRLStoreSelector.Builder selBuilder = new PKIXCRLStoreSelector.Builder(baseDeltaSelect);
 
@@ -1049,8 +1082,57 @@
         PKIXCRLStoreSelector deltaSelect = selBuilder.build();
 
         // find delta CRLs
-        Set temp = CRL_UTIL.findCRLs(deltaSelect, validityDate, certStores, pkixCrlStores);
+        Set temp = PKIXCRLUtil.findCRLs(deltaSelect, validityDate, certStores, pkixCrlStores);
 
+        // if the named CRL store is empty, and we're told to check with CRLDP
+        if (temp.isEmpty() && Properties.isOverrideSet("org.bouncycastle.x509.enableCRLDP"))
+        {
+            CertificateFactory certFact;
+            try
+            {
+                certFact = helper.createCertificateFactory("X.509");
+            }
+            catch (Exception e)
+            {
+                throw new AnnotatedException("cannot create certificate factory: " + e.getMessage(), e);
+            }
+
+            CRLDistPoint id = CRLDistPoint.getInstance(idp);
+            DistributionPoint[] dps = id.getDistributionPoints();
+            for (int i = 0; i < dps.length; i++)
+            {
+                DistributionPointName dpn = dps[i].getDistributionPoint();
+                // look for URIs in fullName
+                if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME)
+                {
+                    GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
+
+                    for (int j = 0; j < genNames.length; j++)
+                    {
+                        GeneralName name = genNames[i];
+                        if (name.getTagNo() == GeneralName.uniformResourceIdentifier)
+                        {
+                            try
+                            {
+                                PKIXCRLStore store = CrlCache.getCrl(certFact, validityDate,
+                                    new URI(((ASN1String)name.getName()).getString()));
+                                if (store != null)
+                                {
+                                    temp = PKIXCRLUtil.findCRLs(deltaSelect, validityDate, Collections.EMPTY_LIST,
+                                        Collections.singletonList(store));
+                                }
+                                break;
+                            }
+                            catch (Exception e)
+                            {
+                                // ignore...  TODO: maybe log
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        
         Set result = new HashSet();
 
         for (Iterator it = temp.iterator(); it.hasNext(); )
@@ -1091,24 +1173,22 @@
      * @throws AnnotatedException if an exception occurs while picking the CRLs
      * or no CRLs are found.
      */
-    protected static Set getCompleteCRLs(DistributionPoint dp, Object cert,
-                                         Date currentDate, PKIXExtendedParameters paramsPKIX)
-        throws AnnotatedException
+    protected static Set getCompleteCRLs(PKIXCertRevocationCheckerParameters params, DistributionPoint dp, Object cert,
+        PKIXExtendedParameters paramsPKIX, Date validityDate)
+        throws AnnotatedException, RecoverableCertPathValidatorException
     {
         X509CRLSelector baseCrlSelect = new X509CRLSelector();
 
         try
         {
             Set issuers = new HashSet();
-
             issuers.add(PrincipalUtils.getEncodedIssuerPrincipal(cert));
 
             CertPathValidatorUtilities.getCRLIssuersFromDistributionPoint(dp, issuers, baseCrlSelect);
         }
         catch (AnnotatedException e)
         {
-            throw new AnnotatedException(
-                "Could not get issuer information from distribution point.", e);
+            throw new AnnotatedException("Could not get issuer information from distribution point.", e);
         }
 
         if (cert instanceof X509Certificate)
@@ -1116,84 +1196,62 @@
             baseCrlSelect.setCertificateChecking((X509Certificate)cert);
         }
 
-        PKIXCRLStoreSelector crlSelect = new PKIXCRLStoreSelector.Builder(baseCrlSelect).setCompleteCRLEnabled(true).build();
+        PKIXCRLStoreSelector crlSelect = new PKIXCRLStoreSelector.Builder(baseCrlSelect).setCompleteCRLEnabled(true)
+            .build();
 
-        Date validityDate = currentDate;
+        Set crls = PKIXCRLUtil.findCRLs(crlSelect, validityDate, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
 
-        if (paramsPKIX.getDate() != null)
-        {
-            validityDate = paramsPKIX.getDate();
-        }
-
-        Set crls = CRL_UTIL.findCRLs(crlSelect, validityDate, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
-
-        checkCRLsNotEmpty(crls, cert);
+        checkCRLsNotEmpty(params, crls, cert);
 
         return crls;
     }
 
-    protected static Date getValidCertDateFromValidityModel(
-        PKIXExtendedParameters paramsPKIX, CertPath certPath, int index)
-        throws AnnotatedException
+    protected static Date getValidCertDateFromValidityModel(Date validityDate, int validityModel, CertPath certPath,
+        int index) throws AnnotatedException
     {
-        if (paramsPKIX.getValidityModel() == PKIXExtendedParameters.CHAIN_VALIDITY_MODEL)
+        if (PKIXExtendedParameters.CHAIN_VALIDITY_MODEL != validityModel || index <= 0)
         {
-            // if end cert use given signing/encryption/... time
-            if (index <= 0)
+            // use given signing/encryption/... time (or current date)
+            return validityDate;
+        }
+
+        X509Certificate issuedCert = (X509Certificate)certPath.getCertificates().get(index - 1);
+
+        if (index - 1 == 0)
+        {
+            // use time when cert was issued, if available
+            ASN1GeneralizedTime dateOfCertgen = null;
+            try
             {
-                return CertPathValidatorUtilities.getValidDate(paramsPKIX);
-                // else use time when previous cert was created
-            }
-            else
-            {
-                if (index - 1 == 0)
+                byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1))
+                    .getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
+                if (extBytes != null)
                 {
-                    ASN1GeneralizedTime dateOfCertgen = null;
-                    try
-                    {
-                        byte[] extBytes = ((X509Certificate)certPath.getCertificates().get(index - 1)).getExtensionValue(ISISMTTObjectIdentifiers.id_isismtt_at_dateOfCertGen.getId());
-                        if (extBytes != null)
-                        {
-                            dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
-                        }
-                    }
-                    catch (IOException e)
-                    {
-                        throw new AnnotatedException(
-                            "Date of cert gen extension could not be read.");
-                    }
-                    catch (IllegalArgumentException e)
-                    {
-                        throw new AnnotatedException(
-                            "Date of cert gen extension could not be read.");
-                    }
-                    if (dateOfCertgen != null)
-                    {
-                        try
-                        {
-                            return dateOfCertgen.getDate();
-                        }
-                        catch (ParseException e)
-                        {
-                            throw new AnnotatedException(
-                                "Date from date of cert gen extension could not be parsed.",
-                                e);
-                        }
-                    }
-                    return ((X509Certificate)certPath.getCertificates().get(
-                        index - 1)).getNotBefore();
+                    dateOfCertgen = ASN1GeneralizedTime.getInstance(ASN1Primitive.fromByteArray(extBytes));
                 }
-                else
+            }
+            catch (IOException e)
+            {
+                throw new AnnotatedException("Date of cert gen extension could not be read.");
+            }
+            catch (IllegalArgumentException e)
+            {
+                throw new AnnotatedException("Date of cert gen extension could not be read.");
+            }
+            if (dateOfCertgen != null)
+            {
+                try
                 {
-                    return ((X509Certificate)certPath.getCertificates().get(
-                        index - 1)).getNotBefore();
+                    return dateOfCertgen.getDate();
+                }
+                catch (ParseException e)
+                {
+                    throw new AnnotatedException("Date from date of cert gen extension could not be parsed.", e);
                 }
             }
         }
-        else
-        {
-            return getValidDate(paramsPKIX);
-        }
+
+        return issuedCert.getNotBefore();
     }
 
     /**
@@ -1306,37 +1364,24 @@
         }
 
         PKIXCertStoreSelector certSelect = new PKIXCertStoreSelector.Builder(selector).build();
-        Set certs = new LinkedHashSet();
-
-        Iterator iter;
+        LinkedHashSet certs = new LinkedHashSet();
 
         try
         {
-            List matches = new ArrayList();
-
-            matches.addAll(CertPathValidatorUtilities.findCertificates(certSelect, certStores));
-            matches.addAll(CertPathValidatorUtilities.findCertificates(certSelect, pkixCertStores));
-
-            iter = matches.iterator();
+            CertPathValidatorUtilities.findCertificates(certs, certSelect, certStores);
+            CertPathValidatorUtilities.findCertificates(certs, certSelect, pkixCertStores);
         }
         catch (AnnotatedException e)
         {
             throw new AnnotatedException("Issuer certificate cannot be searched.", e);
         }
 
-        X509Certificate issuer = null;
-        while (iter.hasNext())
-        {
-            issuer = (X509Certificate)iter.next();
-            // issuer cannot be verified because possible DSA inheritance
-            // parameters are missing
-            certs.add(issuer);
-        }
+        // issuers cannot be verified because possible DSA inheritance parameters are missing
+
         return certs;
     }
 
-    protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey,
-                                                String sigProvider)
+    protected static void verifyX509Certificate(X509Certificate cert, PublicKey publicKey, String sigProvider)
         throws GeneralSecurityException
     {
         if (sigProvider == null)
@@ -1349,8 +1394,8 @@
         }
     }
 
-    static void checkCRLsNotEmpty(Set crls, Object cert)
-        throws AnnotatedException
+    static void checkCRLsNotEmpty(PKIXCertRevocationCheckerParameters params, Set crls, Object cert)
+        throws RecoverableCertPathValidatorException
     {
         if (crls.isEmpty())
         {
@@ -1358,13 +1403,15 @@
             {
                 X509AttributeCertificate aCert = (X509AttributeCertificate)cert;
 
-                throw new AnnotatedException("No CRLs found for issuer \"" + aCert.getIssuer().getPrincipals()[0] + "\"");
+                throw new RecoverableCertPathValidatorException("No CRLs found for issuer \"" + aCert.getIssuer().getPrincipals()[0] + "\"", null,
+                                params.getCertPath(), params.getIndex());
             }
             else
             {
                 X509Certificate xCert = (X509Certificate)cert;
 
-                throw new AnnotatedException("No CRLs found for issuer \"" + RFC4519Style.INSTANCE.toString(PrincipalUtils.getIssuerPrincipal(xCert)) + "\"");
+                throw new RecoverableCertPathValidatorException("No CRLs found for issuer \"" + RFC4519Style.INSTANCE.toString(PrincipalUtils.getIssuerPrincipal(xCert)) + "\"", null,
+                    params.getCertPath(), params.getIndex());
             }
         }
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/CrlCache.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/CrlCache.java
new file mode 100644
index 0000000..ca30e10
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/CrlCache.java
@@ -0,0 +1,193 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+import org.bouncycastle.jcajce.PKIXCRLStore;
+import org.bouncycastle.util.CollectionStore;
+import org.bouncycastle.util.Iterable;
+import org.bouncycastle.util.Selector;
+import org.bouncycastle.util.Store;
+
+class CrlCache
+{
+    private static final int DEFAULT_TIMEOUT = 15000;
+
+    private static Map<URI, WeakReference<PKIXCRLStore>> cache =
+        Collections.synchronizedMap(new WeakHashMap<URI, WeakReference<PKIXCRLStore>>());
+
+    static synchronized PKIXCRLStore getCrl(CertificateFactory certFact, Date validDate, URI distributionPoint)
+        throws IOException, CRLException
+    {
+        PKIXCRLStore crlStore = null;
+
+        WeakReference<PKIXCRLStore> markerRef = (WeakReference)cache.get(distributionPoint);
+        if (markerRef != null)
+        {
+            crlStore = (PKIXCRLStore)markerRef.get();
+        }
+
+        if (crlStore != null)
+        {
+            boolean isExpired = false;
+            for (Iterator it = crlStore.getMatches(null).iterator(); it.hasNext();)
+            {
+                X509CRL crl = (X509CRL)it.next();
+
+                Date nextUpdate = crl.getNextUpdate();
+                if (nextUpdate != null && nextUpdate.before(validDate))
+                {
+                    isExpired = true;
+                    break;
+                }
+            }
+
+            if (!isExpired)
+            {
+                return crlStore;
+            }
+        }
+
+        Collection crls;
+
+        if (distributionPoint.getScheme().equals("ldap"))
+        {
+            crls = getCrlsFromLDAP(certFact, distributionPoint);
+        }
+        else
+        {
+            // http, https, ftp
+            crls = getCrls(certFact, distributionPoint);
+        }
+
+        LocalCRLStore localCRLStore = new LocalCRLStore(new CollectionStore<CRL>(crls));
+
+        cache.put(distributionPoint, new WeakReference<PKIXCRLStore>(localCRLStore));
+
+        return localCRLStore;
+    }
+
+    private static Collection getCrlsFromLDAP(CertificateFactory certFact, URI distributionPoint)
+        throws IOException, CRLException
+    {
+        Map<String, String> env = new Hashtable<String, String>();
+
+        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put(Context.PROVIDER_URL, distributionPoint.toString());
+
+        byte[] val = null;
+        try
+        {
+            DirContext ctx = new InitialDirContext((Hashtable)env);
+            Attributes avals = ctx.getAttributes("");
+            Attribute aval = avals.get("certificateRevocationList;binary");
+            val = (byte[])aval.get();
+        }
+        catch (NamingException e)
+        {
+            throw new CRLException("issue connecting to: " + distributionPoint.toString(), e);
+        }
+
+        if ((val == null) || (val.length == 0))
+        {
+            throw new CRLException("no CRL returned from: " + distributionPoint);
+        }
+        else
+        {
+            return certFact.generateCRLs(new ByteArrayInputStream(val));
+        }
+    }
+
+    private static Collection getCrls(CertificateFactory certFact, URI distributionPoint)
+        throws IOException, CRLException
+    {
+        HttpURLConnection crlCon = (HttpURLConnection)distributionPoint.toURL().openConnection();
+        crlCon.setConnectTimeout(DEFAULT_TIMEOUT);
+        crlCon.setReadTimeout(DEFAULT_TIMEOUT);
+
+        InputStream crlIn = crlCon.getInputStream();
+
+        Collection crls = certFact.generateCRLs(crlIn);
+
+        crlIn.close();
+
+        return crls;
+    }
+
+    private static class LocalCRLStore<T extends CRL>
+        implements PKIXCRLStore, Iterable<CRL>
+    {
+        private Collection<CRL> _local;
+
+        /**
+         * Basic constructor.
+         *
+         * @param collection - initial contents for the store, this is copied.
+         */
+        public LocalCRLStore(
+            Store<CRL> collection)
+        {
+            _local = new ArrayList<CRL>(collection.getMatches(null));
+        }
+
+        /**
+         * Return the matches in the collection for the passed in selector.
+         *
+         * @param selector the selector to match against.
+         * @return a possibly empty collection of matching objects.
+         */
+        public Collection getMatches(Selector selector)
+        {
+            if (selector == null)
+            {
+                return new ArrayList<CRL>(_local);
+            }
+            else
+            {
+                List<CRL> col = new ArrayList<CRL>();
+                Iterator<CRL> iter = _local.iterator();
+
+                while (iter.hasNext())
+                {
+                    CRL obj = iter.next();
+
+                    if (selector.match(obj))
+                    {
+                        col.add(obj);
+                    }
+                }
+
+                return col;
+            }
+        }
+
+        public Iterator<CRL> iterator()
+        {
+            return getMatches(null).iterator();
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/OcspCache.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/OcspCache.java
new file mode 100644
index 0000000..047c3f9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/OcspCache.java
@@ -0,0 +1,223 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Extension;
+import java.security.cert.X509Certificate;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.CertID;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPRequest;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.Request;
+import org.bouncycastle.asn1.ocsp.ResponseBytes;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.ocsp.TBSRequest;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.io.Streams;
+
+class OcspCache
+{
+    private static final int DEFAULT_TIMEOUT = 15000;
+    private static final int DEFAULT_MAX_RESPONSE_SIZE = 32 * 1024;
+
+    private static Map<URI, WeakReference<Map<CertID, OCSPResponse>>> cache
+        = Collections.synchronizedMap(new WeakHashMap<URI, WeakReference<Map<CertID, OCSPResponse>>>());
+
+    static OCSPResponse getOcspResponse(
+        CertID certID, PKIXCertRevocationCheckerParameters parameters,
+        URI ocspResponder, X509Certificate responderCert, List<Extension> ocspExtensions,
+        JcaJceHelper helper)
+        throws CertPathValidatorException
+    {
+        Map<CertID, OCSPResponse> responseMap = null;
+
+        WeakReference<Map<CertID, OCSPResponse>> markerRef = cache.get(ocspResponder);
+        if (markerRef != null)
+        {
+            responseMap = markerRef.get();
+        }
+
+        if (responseMap != null)
+        {
+            OCSPResponse response = responseMap.get(certID);
+            if (response != null)
+            {
+                BasicOCSPResponse basicResp = BasicOCSPResponse.getInstance(
+                    ASN1OctetString.getInstance(response.getResponseBytes().getResponse()).getOctets());
+
+                ResponseData responseData = ResponseData.getInstance(basicResp.getTbsResponseData());
+
+                ASN1Sequence s = responseData.getResponses();
+
+                for (int i = 0; i != s.size(); i++)
+                {
+                    SingleResponse resp = SingleResponse.getInstance(s.getObjectAt(i));
+
+                    if (certID.equals(resp.getCertID()))
+                    {
+                        ASN1GeneralizedTime nextUp = resp.getNextUpdate();
+                        try
+                        {
+                            if (nextUp != null && parameters.getValidDate().after(nextUp.getDate()))
+                            {
+                                responseMap.remove(certID);
+                                response = null;
+                            }
+                        }
+                        catch (ParseException e)
+                        {
+                            // this should never happen, but...
+                            responseMap.remove(certID);
+                            response = null;
+                        }
+                    }
+                }
+                if (response != null)
+                {
+                    return response;
+                }
+            }
+        }
+
+        URL ocspUrl;
+        try
+        {
+            ocspUrl = ocspResponder.toURL();
+        }
+        catch (MalformedURLException e)
+        {
+            throw new CertPathValidatorException("configuration error: " + e.getMessage(),
+                e, parameters.getCertPath(), parameters.getIndex());
+        }
+
+        //
+        // basic request generation
+        //
+        ASN1EncodableVector requests = new ASN1EncodableVector();
+
+        requests.add(new Request(certID, null));
+
+        List exts = ocspExtensions;
+        ASN1EncodableVector requestExtensions = new ASN1EncodableVector();
+
+        byte[] nonce = null;
+        for (int i = 0; i != exts.size(); i++)
+        {
+            Extension ext = (Extension)exts.get(i);
+            byte[] value = ext.getValue();
+
+            if (OCSPObjectIdentifiers.id_pkix_ocsp_nonce.getId().equals(ext.getId()))
+            {
+                nonce = value;
+            }
+
+            requestExtensions.add(new org.bouncycastle.asn1.x509.Extension(
+                new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), value));
+        }
+
+        // TODO: configure originator
+        TBSRequest tbsReq = new TBSRequest(null, new DERSequence(requests),
+            Extensions.getInstance(new DERSequence(requestExtensions)));
+
+        org.bouncycastle.asn1.ocsp.Signature signature = null;
+
+        try
+        {
+
+            byte[] request = new OCSPRequest(tbsReq, signature).getEncoded();
+
+            HttpURLConnection ocspCon = (HttpURLConnection)ocspUrl.openConnection();
+            ocspCon.setConnectTimeout(DEFAULT_TIMEOUT);
+            ocspCon.setReadTimeout(DEFAULT_TIMEOUT);
+            ocspCon.setDoOutput(true);
+            ocspCon.setDoInput(true);
+            ocspCon.setRequestMethod("POST");
+            ocspCon.setRequestProperty("Content-type", "application/ocsp-request");
+            ocspCon.setRequestProperty("Content-length", String.valueOf(request.length));
+
+            OutputStream reqOut = ocspCon.getOutputStream();
+            reqOut.write(request);
+            reqOut.flush();
+
+            InputStream reqIn = ocspCon.getInputStream();
+            int contentLength = ocspCon.getContentLength();
+            if (contentLength < 0)
+            {
+                // TODO: make configurable
+                contentLength = DEFAULT_MAX_RESPONSE_SIZE;
+            }
+            OCSPResponse response = OCSPResponse.getInstance(Streams.readAllLimited(reqIn, contentLength));
+
+            if (OCSPResponseStatus.SUCCESSFUL == response.getResponseStatus().getIntValue())
+            {
+                boolean validated = false;
+                ResponseBytes respBytes = ResponseBytes.getInstance(response.getResponseBytes());
+
+                if (respBytes.getResponseType().equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic))
+                {
+                    BasicOCSPResponse basicResp = BasicOCSPResponse.getInstance(respBytes.getResponse().getOctets());
+
+                    validated = ProvOcspRevocationChecker.validatedOcspResponse(basicResp, parameters, nonce, responderCert, helper);
+                }
+
+                if (!validated)
+                {
+                    throw new CertPathValidatorException(
+                        "OCSP response failed to validate", null, parameters.getCertPath(), parameters.getIndex());
+                }
+
+                markerRef = cache.get(ocspResponder);
+                if (markerRef != null)
+                {
+                    responseMap = markerRef.get();
+                    responseMap.put(certID, response);
+                }
+                else
+                {
+                    responseMap = new HashMap<CertID, OCSPResponse>();
+                    responseMap.put(certID, response);
+                    cache.put(ocspResponder, new WeakReference<Map<CertID, OCSPResponse>>(responseMap));
+                }
+
+                return response;
+            }
+            else
+            {
+                throw new CertPathValidatorException(
+                    "OCSP responder failed: " + response.getResponseStatus().getValue(),
+                    null, parameters.getCertPath(), parameters.getIndex());
+            }
+        }
+        catch (IOException e)
+        {
+            throw new CertPathValidatorException("configuration error: " + e.getMessage(),
+                     e, parameters.getCertPath(), parameters.getIndex());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
index bcc258c..3d2f8f2 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathBuilderSpi.java
@@ -1,6 +1,5 @@
 package org.bouncycastle.jce.provider;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.Principal;
@@ -10,10 +9,6 @@
 import java.security.cert.CertPathBuilderSpi;
 import java.security.cert.CertPathParameters;
 import java.security.cert.CertPathValidator;
-import java.security.cert.CertStore;
-import java.security.cert.CertStoreException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.PKIXBuilderParameters;
@@ -24,6 +19,7 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -32,8 +28,8 @@
 import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.jcajce.PKIXCertStoreSelector;
 import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
+import org.bouncycastle.jcajce.PKIXExtendedParameters;
 import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
-import org.bouncycastle.util.Encodable;
 import org.bouncycastle.util.Selector;
 import org.bouncycastle.util.Store;
 import org.bouncycastle.util.StoreException;
@@ -42,7 +38,6 @@
 import org.bouncycastle.x509.X509AttributeCertStoreSelector;
 import org.bouncycastle.x509.X509AttributeCertificate;
 import org.bouncycastle.x509.X509CertStoreSelector;
-import org.bouncycastle.x509.X509Store;
 
 public class PKIXAttrCertPathBuilderSpi
     extends CertPathBuilderSpi
@@ -98,7 +93,8 @@
 
         // search target certificates
 
-        Selector certSelect = paramsPKIX.getBaseParameters().getTargetConstraints();
+        PKIXExtendedParameters baseParams = paramsPKIX.getBaseParameters();
+        Selector certSelect = baseParams.getTargetConstraints();
         if (!(certSelect instanceof X509AttributeCertStoreSelector))
         {
             throw new CertPathBuilderException(
@@ -120,7 +116,7 @@
         if (targets.isEmpty())
         {
             throw new CertPathBuilderException(
-                    "No attribute certificate found matching targetContraints.");
+                    "No attribute certificate found matching targetConstraints.");
         }
 
         CertPathBuilderResult result = null;
@@ -133,7 +129,7 @@
             
             X509CertStoreSelector selector = new X509CertStoreSelector();
             Principal[] principals = cert.getIssuer().getPrincipals();
-            Set issuers = new HashSet();
+            LinkedHashSet issuers = new LinkedHashSet();
             for (int i = 0; i < principals.length; i++)
             {
                 try
@@ -143,8 +139,8 @@
                         selector.setSubject(((X500Principal)principals[i]).getEncoded());
                     }
                     PKIXCertStoreSelector certStoreSelector = new PKIXCertStoreSelector.Builder(selector).build();
-                    issuers.addAll(CertPathValidatorUtilities.findCertificates(certStoreSelector, paramsPKIX.getBaseParameters().getCertStores()));
-                    issuers.addAll(CertPathValidatorUtilities.findCertificates(certStoreSelector, paramsPKIX.getBaseParameters().getCertificateStores()));
+                    CertPathValidatorUtilities.findCertificates(issuers, certStoreSelector, baseParams.getCertStores());
+                    CertPathValidatorUtilities.findCertificates(issuers, certStoreSelector, baseParams.getCertificateStores());
                 }
                 catch (AnnotatedException e)
                 {
@@ -236,82 +232,78 @@
         try
         {
             // check whether the issuer of <tbvCert> is a TrustAnchor
-            if (CertPathValidatorUtilities.isIssuerTrustAnchor(tbvCert, pkixParams.getBaseParameters().getTrustAnchors(),
-                pkixParams.getBaseParameters().getSigProvider()))
+            PKIXExtendedParameters baseParams = pkixParams.getBaseParameters();
+            if (CertPathValidatorUtilities.isIssuerTrustAnchor(tbvCert, baseParams.getTrustAnchors(),
+                baseParams.getSigProvider()))
             {
                 CertPath certPath;
-                PKIXCertPathValidatorResult result;
                 try
                 {
                     certPath = cFact.generateCertPath(tbvPath);
                 }
                 catch (Exception e)
                 {
-                    throw new AnnotatedException(
-                                            "Certification path could not be constructed from certificate list.",
-                                            e);
+                    throw new AnnotatedException("Certification path could not be constructed from certificate list.",
+                        e);
                 }
 
+                PKIXCertPathValidatorResult result;
                 try
                 {
-                    result = (PKIXCertPathValidatorResult) validator.validate(
-                            certPath, pkixParams);
+                    result = (PKIXCertPathValidatorResult)validator.validate(certPath, pkixParams);
                 }
                 catch (Exception e)
                 {
-                    throw new AnnotatedException(
-                                            "Certification path could not be validated.",
-                                            e);
+                    throw new AnnotatedException("Certification path could not be validated.", e);
                 }
 
-                return new PKIXCertPathBuilderResult(certPath, result
-                        .getTrustAnchor(), result.getPolicyTree(), result
-                        .getPublicKey());
+                return new PKIXCertPathBuilderResult(certPath, result.getTrustAnchor(), result.getPolicyTree(),
+                    result.getPublicKey());
 
             }
             else
             {
                 List stores = new ArrayList();
+                stores.addAll(baseParams.getCertificateStores());
 
-                stores.addAll(pkixParams.getBaseParameters().getCertificateStores());
                 // add additional X.509 stores from locations in certificate
                 try
                 {
-                    stores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromAltNames(tbvCert.getExtensionValue(Extension.issuerAlternativeName.getId()), pkixParams.getBaseParameters().getNamedCertificateStoreMap()));
+                    stores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromAltNames(
+                        tbvCert.getExtensionValue(Extension.issuerAlternativeName.getId()),
+                        baseParams.getNamedCertificateStoreMap()));
                 }
                 catch (CertificateParsingException e)
                 {
-                    throw new AnnotatedException(
-                                            "No additional X.509 stores can be added from certificate locations.",
-                                            e);
+                    throw new AnnotatedException("No additional X.509 stores can be added from certificate locations.",
+                        e);
                 }
+
                 Collection issuers = new HashSet();
                 // try to get the issuer certificate from one
                 // of the stores
                 try
                 {
-                    issuers.addAll(CertPathValidatorUtilities.findIssuerCerts(tbvCert, pkixParams.getBaseParameters().getCertStores(), stores));
+                    issuers.addAll(CertPathValidatorUtilities.findIssuerCerts(tbvCert, baseParams.getCertStores(), stores));
                 }
                 catch (AnnotatedException e)
                 {
                     throw new AnnotatedException(
-                                            "Cannot find issuer certificate for certificate in certification path.",
-                                            e);
+                        "Cannot find issuer certificate for certificate in certification path.", e);
                 }
                 if (issuers.isEmpty())
                 {
                     throw new AnnotatedException(
                             "No issuer certificate for certificate in certification path found.");
                 }
-                Iterator it = issuers.iterator();
 
+                Iterator it = issuers.iterator();
                 while (it.hasNext() && builderResult == null)
                 {
                     X509Certificate issuer = (X509Certificate) it.next();
                     // TODO Use CertPathValidatorUtilities.isSelfIssued(issuer)?
                     // if untrusted self signed certificate continue
-                    if (issuer.getIssuerX500Principal().equals(
-                            issuer.getSubjectX500Principal()))
+                    if (issuer.getIssuerX500Principal().equals(issuer.getSubjectX500Principal()))
                     {
                         continue;
                     }
@@ -321,8 +313,7 @@
         }
         catch (AnnotatedException e)
         {
-            certPathException = new AnnotatedException(
-                            "No valid certification path could be build.", e);
+            certPathException = new AnnotatedException("No valid certification path could be build.", e);
         }
         if (builderResult == null)
         {
@@ -331,17 +322,15 @@
         return builderResult;
     }
 
-    protected static Collection findCertificates(X509AttributeCertStoreSelector certSelect,
-                                                     List certStores)
+    protected static Collection findCertificates(X509AttributeCertStoreSelector certSelect, List certStores)
         throws AnnotatedException
     {
         Set certs = new HashSet();
-        Iterator iter = certStores.iterator();
 
+        Iterator iter = certStores.iterator();
         while (iter.hasNext())
         {
             Object obj = iter.next();
-
             if (obj instanceof Store)
             {
                 Store certStore = (Store)obj;
@@ -351,11 +340,11 @@
                 }
                 catch (StoreException e)
                 {
-                    throw new AnnotatedException(
-                            "Problem while picking certificates from X.509 store.", e);
+                    throw new AnnotatedException("Problem while picking certificates from X.509 store.", e);
                 }
             }
         }
+
         return certs;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
index ee72703..6f9cb7b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXAttrCertPathValidatorSpi.java
@@ -1,8 +1,6 @@
 package org.bouncycastle.jce.provider;
 
 import java.security.InvalidAlgorithmParameterException;
-import java.security.Provider;
-import java.security.Security;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathParameters;
 import java.security.cert.CertPathValidatorException;
@@ -99,6 +97,9 @@
             paramsPKIX = (PKIXExtendedParameters)params;
         }
 
+        final Date currentDate = new Date();
+        final Date validityDate = CertPathValidatorUtilities.getValidityDate(paramsPKIX, currentDate);
+
         Selector certSelect = paramsPKIX.getTargetConstraints();
         if (!(certSelect instanceof X509AttributeCertStoreSelector))
         {
@@ -117,21 +118,13 @@
             .getCertificates().get(0);
         RFC3281CertPathUtilities.processAttrCert3(issuerCert, paramsPKIX);
         RFC3281CertPathUtilities.processAttrCert4(issuerCert, trustedACIssuers);
-        RFC3281CertPathUtilities.processAttrCert5(attrCert, paramsPKIX);
+        RFC3281CertPathUtilities.processAttrCert5(attrCert, validityDate);
         // 6 already done in X509AttributeCertStoreSelector
         RFC3281CertPathUtilities.processAttrCert7(attrCert, certPath, holderCertPath, paramsPKIX, attrCertCheckers);
         RFC3281CertPathUtilities.additionalChecks(attrCert, prohibitedACAttrbiutes, necessaryACAttributes);
-        Date date = null;
-        try
-        {
-            date = CertPathValidatorUtilities.getValidCertDateFromValidityModel(paramsPKIX, null, -1);
-        }
-        catch (AnnotatedException e)
-        {
-            throw new ExtCertPathValidatorException(
-                "Could not get validity date from attribute certificate.", e);
-        }
-        RFC3281CertPathUtilities.checkCRLs(attrCert, paramsPKIX, issuerCert, date, certPath.getCertificates(), helper);
+
+        RFC3281CertPathUtilities.checkCRLs(attrCert, paramsPKIX, currentDate, validityDate, issuerCert,
+            certPath.getCertificates(), helper);
         return result;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCRLUtil.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCRLUtil.java
index f43e185..5a4ce21 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCRLUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCRLUtil.java
@@ -16,18 +16,18 @@
 import org.bouncycastle.util.Store;
 import org.bouncycastle.util.StoreException;
 
-class PKIXCRLUtil
+abstract class PKIXCRLUtil
 {
-    public Set findCRLs(PKIXCRLStoreSelector crlselect, Date validityDate, List certStores, List pkixCrlStores)
+    static Set findCRLs(PKIXCRLStoreSelector crlselect, Date validityDate, List certStores, List pkixCrlStores)
         throws AnnotatedException
     {
-        Set initialSet = new HashSet();
+        HashSet initialSet = new HashSet();
 
         // get complete CRL(s)
         try
         {
-            initialSet.addAll(findCRLs(crlselect, pkixCrlStores));
-            initialSet.addAll(findCRLs(crlselect, certStores));
+            findCRLs(initialSet, crlselect, pkixCrlStores);
+            findCRLs(initialSet, crlselect, certStores);
         }
         catch (AnnotatedException e)
         {
@@ -45,14 +45,7 @@
             {
                 X509Certificate cert = crlselect.getCertificateChecking();
 
-                if (cert != null)
-                {
-                    if (crl.getThisUpdate().before(cert.getNotAfter()))
-                    {
-                        finalSet.add(crl);
-                    }
-                }
-                else
+                if (null == cert || crl.getThisUpdate().before(cert.getNotAfter()))
                 {
                     finalSet.add(crl);
                 }
@@ -63,31 +56,26 @@
     }
 
     /**
-     * Return a Collection of all CRLs found in the X509Store's that are
-     * matching the crlSelect criteriums.
+     * Add to a HashSet any and all CRLs found in the X509Store's that are matching the crlSelect
+     * critera.
      *
-     * @param crlSelect a {@link org.bouncycastle.jcajce.PKIXCRLStoreSelector} object that will be used
-     *            to select the CRLs
-     * @param crlStores a List containing only
-     *            {@link Store} objects.
-     *            These are used to search for CRLs
-     *
-     * @return a Collection of all found {@link java.security.cert.X509CRL X509CRL} objects. May be
-     *         empty but never <code>null</code>.
+     * @param crls
+     *            the {@link HashSet} to add the CRLs to.
+     * @param crlSelect
+     *            a {@link org.bouncycastle.jcajce.PKIXCRLStoreSelector} object that will be used to
+     *            select the CRLs
+     * @param crlStores
+     *            a List containing only {@link Store} objects. These are used to search for CRLs
      */
-    private final Collection findCRLs(PKIXCRLStoreSelector crlSelect,
-        List crlStores) throws AnnotatedException
+    private static void findCRLs(HashSet crls, PKIXCRLStoreSelector crlSelect, List crlStores) throws AnnotatedException
     {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
-
         AnnotatedException lastException = null;
         boolean foundValidStore = false;
 
+        Iterator iter = crlStores.iterator();
         while (iter.hasNext())
         {
             Object obj = iter.next();
-
             if (obj instanceof Store)
             {
                 Store store = (Store)obj;
@@ -99,8 +87,7 @@
                 }
                 catch (StoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
             else
@@ -114,16 +101,14 @@
                 }
                 catch (CertStoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
         }
+
         if (!foundValidStore && lastException != null)
         {
             throw lastException;
         }
-        return crls;
     }
-
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
index 26ee998..9ba3a38 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi.java
@@ -19,11 +19,9 @@
 
 import org.bouncycastle.asn1.x509.Extension;
 import org.bouncycastle.jcajce.PKIXCertStore;
-import org.bouncycastle.jcajce.PKIXCertStoreSelector;
 import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
 import org.bouncycastle.jcajce.PKIXExtendedParameters;
 import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
-import org.bouncycastle.jce.exception.ExtCertPathBuilderException;
 import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
 import org.bouncycastle.x509.ExtendedPKIXParameters;
 
@@ -100,26 +98,7 @@
         X509Certificate cert;
 
         // search target certificates
-
-        PKIXCertStoreSelector certSelect = paramsPKIX.getBaseParameters().getTargetConstraints();
-
-        try
-        {
-            targets = CertPathValidatorUtilities.findCertificates(certSelect, paramsPKIX.getBaseParameters().getCertificateStores());
-            targets.addAll(CertPathValidatorUtilities.findCertificates(certSelect, paramsPKIX.getBaseParameters().getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new ExtCertPathBuilderException(
-                "Error finding target certificate.", e);
-        }
-
-        if (targets.isEmpty())
-        {
-
-            throw new CertPathBuilderException(
-                "No certificate found matching targetContraints.");
-        }
+        targets = CertPathValidatorUtilities.findTargets(paramsPKIX);
 
         CertPathBuilderResult result = null;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi_8.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi_8.java
new file mode 100644
index 0000000..32bb511
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathBuilderSpi_8.java
@@ -0,0 +1,281 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathBuilderSpi;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXCertPathBuilderResult;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.PKIXCertPathValidatorResult;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.jcajce.PKIXCertStore;
+import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
+import org.bouncycastle.jcajce.PKIXExtendedParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
+import org.bouncycastle.jcajce.util.BCJcaJceHelper;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+import org.bouncycastle.x509.ExtendedPKIXBuilderParameters;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+
+/**
+ * Implements the PKIX CertPathBuilding algorithm for BouncyCastle.
+ * 
+ * @see CertPathBuilderSpi
+ */
+public class PKIXCertPathBuilderSpi_8
+    extends CertPathBuilderSpi
+{
+    private final JcaJceHelper helper = new BCJcaJceHelper();
+    private final boolean isForCRLCheck;
+
+    public PKIXCertPathBuilderSpi_8()
+    {
+        this(false);
+    }
+
+    PKIXCertPathBuilderSpi_8(boolean isForCRLCheck)
+    {
+        this.isForCRLCheck = isForCRLCheck;
+    }
+
+    public PKIXCertPathChecker engineGetRevocationChecker()
+    {
+        return new ProvRevocationChecker(helper);
+    }
+
+    /**
+     * Build and validate a CertPath using the given parameter.
+     * 
+     * @param params PKIXBuilderParameters object containing all information to
+     *            build the CertPath
+     */
+    public CertPathBuilderResult engineBuild(CertPathParameters params)
+        throws CertPathBuilderException, InvalidAlgorithmParameterException
+    {
+        PKIXExtendedBuilderParameters paramsPKIX;
+        if (params instanceof PKIXBuilderParameters)
+        {
+            PKIXExtendedParameters.Builder paramsPKIXBldr = new PKIXExtendedParameters.Builder((PKIXBuilderParameters)params);
+            PKIXExtendedBuilderParameters.Builder paramsBldrPKIXBldr;
+
+            if (params instanceof ExtendedPKIXParameters)
+            {
+                ExtendedPKIXBuilderParameters extPKIX = (ExtendedPKIXBuilderParameters)params;
+
+                for (Iterator it = extPKIX.getAdditionalStores().iterator(); it.hasNext();)
+                {
+                     paramsPKIXBldr.addCertificateStore((PKIXCertStore)it.next());
+                }
+                paramsBldrPKIXBldr  = new PKIXExtendedBuilderParameters.Builder(paramsPKIXBldr.build());
+
+                paramsBldrPKIXBldr.addExcludedCerts(extPKIX.getExcludedCerts());
+                paramsBldrPKIXBldr.setMaxPathLength(extPKIX.getMaxPathLength());
+            }
+            else
+            {
+                paramsBldrPKIXBldr  = new PKIXExtendedBuilderParameters.Builder((PKIXBuilderParameters)params);
+            }
+
+            paramsPKIX = paramsBldrPKIXBldr.build();
+        }
+        else if (params instanceof PKIXExtendedBuilderParameters)
+        {
+            paramsPKIX = (PKIXExtendedBuilderParameters)params;
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException(
+                "Parameters must be an instance of "
+                    + PKIXBuilderParameters.class.getName() + " or "
+                    + PKIXExtendedBuilderParameters.class.getName() + ".");
+        }
+
+        Collection targets;
+        Iterator targetIter;
+        List certPathList = new ArrayList();
+        X509Certificate cert;
+
+        // search target certificates
+        targets = CertPathValidatorUtilities.findTargets(paramsPKIX);
+
+        CertPathBuilderResult result = null;
+
+        // check all potential target certificates
+        targetIter = targets.iterator();
+        while (targetIter.hasNext() && result == null)
+        {
+            cert = (X509Certificate) targetIter.next();
+            result = build(cert, paramsPKIX, certPathList);
+        }
+
+        if (result == null && certPathException != null)
+        {
+            if (certPathException instanceof AnnotatedException)
+            {
+                throw new CertPathBuilderException(certPathException.getMessage(), certPathException.getCause());
+            }
+            throw new CertPathBuilderException(
+                "Possible certificate chain could not be validated.",
+                certPathException);
+        }
+
+        if (result == null && certPathException == null)
+        {
+            throw new CertPathBuilderException(
+                "Unable to find certificate chain.");
+        }
+
+        return result;
+    }
+
+    private Exception certPathException;
+
+    protected CertPathBuilderResult build(X509Certificate tbvCert,
+        PKIXExtendedBuilderParameters pkixParams, List tbvPath)
+    {
+        // If tbvCert is readily present in tbvPath, it indicates having run
+        // into a cycle in the
+        // PKI graph.
+        if (tbvPath.contains(tbvCert))
+        {
+            return null;
+        }
+        // step out, the certificate is not allowed to appear in a certification
+        // chain.
+        if (pkixParams.getExcludedCerts().contains(tbvCert))
+        {
+            return null;
+        }
+        // test if certificate path exceeds maximum length
+        if (pkixParams.getMaxPathLength() != -1)
+        {
+            if (tbvPath.size() - 1 > pkixParams.getMaxPathLength())
+            {
+                return null;
+            }
+        }
+
+        tbvPath.add(tbvCert);
+
+        CertPathBuilderResult builderResult = null;
+
+        try
+        {
+            CertificateFactory cFact;
+            PKIXCertPathValidatorSpi_8 validator;
+
+            try
+            {
+                cFact = new CertificateFactory();
+                validator = new PKIXCertPathValidatorSpi_8(isForCRLCheck);
+            }
+            catch (Exception e)
+            {
+                // cannot happen
+                throw new RuntimeException("Exception creating support classes.");
+            }
+
+            // check whether the issuer of <tbvCert> is a TrustAnchor
+            if (CertPathValidatorUtilities.isIssuerTrustAnchor(tbvCert, pkixParams.getBaseParameters().getTrustAnchors(),
+                pkixParams.getBaseParameters().getSigProvider()))
+            {
+                // exception message from possibly later tried certification
+                // chains
+                CertPath certPath = null;
+                PKIXCertPathValidatorResult result = null;
+                try
+                {
+                    certPath = cFact.engineGenerateCertPath(tbvPath);
+                }
+                catch (Exception e)
+                {
+                    throw new AnnotatedException(
+                        "Certification path could not be constructed from certificate list.",
+                        e);
+                }
+
+                try
+                {
+                    result = (PKIXCertPathValidatorResult) validator.engineValidate(
+                        certPath, pkixParams);
+                }
+                catch (Exception e)
+                {
+                    throw new AnnotatedException(
+                        "Certification path could not be validated.", e);
+                }
+
+                return new PKIXCertPathBuilderResult(certPath, result
+                    .getTrustAnchor(), result.getPolicyTree(), result
+                    .getPublicKey());
+
+            }
+            else
+            {
+                List stores = new ArrayList();
+
+
+                stores.addAll(pkixParams.getBaseParameters().getCertificateStores());
+
+                // add additional X.509 stores from locations in certificate
+                try
+                {
+                    stores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromAltNames(
+                        tbvCert.getExtensionValue(Extension.issuerAlternativeName.getId()), pkixParams.getBaseParameters().getNamedCertificateStoreMap()));
+                }
+                catch (CertificateParsingException e)
+                {
+                    throw new AnnotatedException(
+                        "No additional X.509 stores can be added from certificate locations.",
+                        e);
+                }
+                Collection issuers = new HashSet();
+                // try to get the issuer certificate from one
+                // of the stores
+                try
+                {
+                    issuers.addAll(CertPathValidatorUtilities.findIssuerCerts(tbvCert, pkixParams.getBaseParameters().getCertStores(), stores));
+                }
+                catch (AnnotatedException e)
+                {
+                    throw new AnnotatedException(
+                        "Cannot find issuer certificate for certificate in certification path.",
+                        e);
+                }
+                if (issuers.isEmpty())
+                {
+                    throw new AnnotatedException(
+                        "No issuer certificate for certificate in certification path found.");
+                }
+                Iterator it = issuers.iterator();
+
+                while (it.hasNext() && builderResult == null)
+                {
+                    X509Certificate issuer = (X509Certificate) it.next();
+                    builderResult = build(issuer, pkixParams, tbvPath);
+                }
+            }
+        }
+        catch (AnnotatedException e)
+        {
+            certPathException = e;
+        }
+        if (builderResult == null)
+        {
+            tbvPath.remove(tbvCert);
+        }
+        return builderResult;
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
index a58f392..b79e499 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi.java
@@ -14,6 +14,7 @@
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -111,7 +112,8 @@
         //
         // (b)
         //
-        // Date validDate = CertPathValidatorUtilities.getValidDate(paramsPKIX);
+        final Date currentDate = new Date();
+        final Date validityDate = CertPathValidatorUtilities.getValidityDate(paramsPKIX, currentDate);
 
         //
         // (c)
@@ -284,6 +286,19 @@
             ((PKIXCertPathChecker) certIter.next()).init(false);
         }
 
+        //
+        // initialize RevocationChecker
+        //
+        ProvCrlRevocationChecker revocationChecker;
+        if (paramsPKIX.isRevocationEnabled())
+        {
+            revocationChecker = new ProvCrlRevocationChecker(helper);
+        }
+        else
+        {
+            revocationChecker = null;
+        }
+
         X509Certificate cert = null;
 
         for (index = certs.size() - 1; index >= 0; index--)
@@ -317,8 +332,8 @@
             // 6.1.3
             //
 
-            RFC3280CertPathUtilities.processCertA(certPath, paramsPKIX, index, workingPublicKey,
-                verificationAlreadyPerformed, workingIssuerName, sign, helper);
+            RFC3280CertPathUtilities.processCertA(certPath, paramsPKIX, validityDate, revocationChecker, index,
+                workingPublicKey, verificationAlreadyPerformed, workingIssuerName, sign);
 
             RFC3280CertPathUtilities.processCertBC(certPath, index, nameConstraintValidator, isForCRLCheck);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi_8.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi_8.java
new file mode 100644
index 0000000..f40c4c6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/PKIXCertPathValidatorSpi_8.java
@@ -0,0 +1,549 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.PublicKey;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertPathValidatorResult;
+import java.security.cert.CertPathValidatorSpi;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.PKIXCertPathChecker;
+import java.security.cert.PKIXCertPathValidatorResult;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
+import org.bouncycastle.jcajce.PKIXExtendedParameters;
+import org.bouncycastle.jcajce.interfaces.BCX509Certificate;
+import org.bouncycastle.jcajce.util.BCJcaJceHelper;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.x509.ExtendedPKIXParameters;
+
+/**
+ * CertPathValidatorSpi implementation for X.509 Certificate validation � la RFC
+ * 3280.
+ */
+public class PKIXCertPathValidatorSpi_8
+        extends CertPathValidatorSpi
+{
+    private final JcaJceHelper helper = new BCJcaJceHelper();
+    private final boolean isForCRLCheck;
+
+    public PKIXCertPathValidatorSpi_8()
+    {
+        this(false);
+    }
+
+    public PKIXCertPathValidatorSpi_8(boolean isForCRLCheck)
+    {
+        this.isForCRLCheck = isForCRLCheck;
+    }
+
+    public PKIXCertPathChecker engineGetRevocationChecker()
+    {
+        return new ProvRevocationChecker(helper);
+    }
+
+    public CertPathValidatorResult engineValidate(
+            CertPath certPath,
+            CertPathParameters params)
+            throws CertPathValidatorException,
+            InvalidAlgorithmParameterException
+    {
+        PKIXExtendedParameters paramsPKIX;
+        if (params instanceof PKIXParameters)
+        {
+            PKIXExtendedParameters.Builder paramsPKIXBldr = new PKIXExtendedParameters.Builder((PKIXParameters)params);
+
+            if (params instanceof ExtendedPKIXParameters)
+            {
+                ExtendedPKIXParameters extPKIX = (ExtendedPKIXParameters)params;
+
+                paramsPKIXBldr.setUseDeltasEnabled(extPKIX.isUseDeltasEnabled());
+                paramsPKIXBldr.setValidityModel(extPKIX.getValidityModel());
+            }
+
+            paramsPKIX = paramsPKIXBldr.build();
+        }
+        else if (params instanceof PKIXExtendedBuilderParameters)
+        {
+            paramsPKIX = ((PKIXExtendedBuilderParameters)params).getBaseParameters();
+        }
+        else if (params instanceof PKIXExtendedParameters)
+        {
+            paramsPKIX = (PKIXExtendedParameters)params;
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("Parameters must be a " + PKIXParameters.class.getName() + " instance.");
+        }
+
+        if (paramsPKIX.getTrustAnchors() == null)
+        {
+            throw new InvalidAlgorithmParameterException(
+                    "trustAnchors is null, this is not allowed for certification path validation.");
+        }
+
+        //
+        // 6.1.1 - inputs
+        //
+
+        //
+        // (a)
+        //
+        List certs = certPath.getCertificates();
+        int n = certs.size();
+
+        if (certs.isEmpty())
+        {
+            throw new CertPathValidatorException("Certification path is empty.", null, certPath, -1);
+        }
+
+        //
+        // (b)
+        //
+        final Date currentDate = new Date();
+        final Date validityDate = CertPathValidatorUtilities.getValidityDate(paramsPKIX, currentDate);
+
+        //
+        // (c)
+        //
+        Set userInitialPolicySet = paramsPKIX.getInitialPolicies();
+
+        //
+        // (d)
+        // 
+        TrustAnchor trust;
+        try
+        {
+            trust = CertPathValidatorUtilities.findTrustAnchor((X509Certificate) certs.get(certs.size() - 1),
+                    paramsPKIX.getTrustAnchors(), paramsPKIX.getSigProvider());
+
+            if (trust == null)
+            {
+                throw new CertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1);
+            }
+
+            checkCertificate(trust.getTrustedCert());
+        }
+        catch (AnnotatedException e)
+        {
+            throw new CertPathValidatorException(e.getMessage(), e.getUnderlyingException(), certPath, certs.size() - 1);
+        }
+
+        // RFC 5280 - CRLs must originate from the same trust anchor as the target certificate.
+        paramsPKIX = new PKIXExtendedParameters.Builder(paramsPKIX).setTrustAnchor(trust).build();
+
+        PKIXCertRevocationChecker revocationChecker = null;
+        List pathCheckers = new ArrayList();
+        Iterator certIter = paramsPKIX.getCertPathCheckers().iterator();
+        while (certIter.hasNext())
+        {
+            PKIXCertPathChecker checker = (PKIXCertPathChecker)certIter.next();
+
+            checker.init(false);
+
+            if (checker instanceof PKIXRevocationChecker)
+            {
+                if (revocationChecker != null)
+                {
+                    throw new CertPathValidatorException("only one PKIXRevocationChecker allowed");
+                }
+                revocationChecker = (checker instanceof PKIXCertRevocationChecker)
+                    ? (PKIXCertRevocationChecker)checker : new WrappedRevocationChecker(checker);
+            }
+            else
+            {
+                pathCheckers.add(checker);
+            }
+        }
+
+        if (paramsPKIX.isRevocationEnabled() && revocationChecker == null)
+        {
+            revocationChecker = new ProvRevocationChecker(helper);
+        }
+
+        //
+        // (e), (f), (g) are part of the paramsPKIX object.
+        //
+
+        int index = 0;
+        int i;
+        // Certificate for each interation of the validation loop
+        // Signature information for each iteration of the validation loop
+        //
+        // 6.1.2 - setup
+        //
+
+        //
+        // (a)
+        //
+        List[] policyNodes = new ArrayList[n + 1];
+        for (int j = 0; j < policyNodes.length; j++)
+        {
+            policyNodes[j] = new ArrayList();
+        }
+
+        Set policySet = new HashSet();
+
+        policySet.add(RFC3280CertPathUtilities.ANY_POLICY);
+
+        PKIXPolicyNode validPolicyTree = new PKIXPolicyNode(new ArrayList(), 0, policySet, null, new HashSet(),
+                RFC3280CertPathUtilities.ANY_POLICY, false);
+
+        policyNodes[0].add(validPolicyTree);
+
+        //
+        // (b) and (c)
+        //
+        PKIXNameConstraintValidator nameConstraintValidator = new PKIXNameConstraintValidator();
+
+        // (d)
+        //
+        int explicitPolicy;
+        Set acceptablePolicies = new HashSet();
+
+        if (paramsPKIX.isExplicitPolicyRequired())
+        {
+            explicitPolicy = 0;
+        }
+        else
+        {
+            explicitPolicy = n + 1;
+        }
+
+        //
+        // (e)
+        //
+        int inhibitAnyPolicy;
+
+        if (paramsPKIX.isAnyPolicyInhibited())
+        {
+            inhibitAnyPolicy = 0;
+        }
+        else
+        {
+            inhibitAnyPolicy = n + 1;
+        }
+
+        //
+        // (f)
+        //
+        int policyMapping;
+
+        if (paramsPKIX.isPolicyMappingInhibited())
+        {
+            policyMapping = 0;
+        }
+        else
+        {
+            policyMapping = n + 1;
+        }
+
+        //
+        // (g), (h), (i), (j)
+        //
+        PublicKey workingPublicKey;
+        X500Name workingIssuerName;
+
+        X509Certificate sign = trust.getTrustedCert();
+        try
+        {
+            if (sign != null)
+            {
+                workingIssuerName = PrincipalUtils.getSubjectPrincipal(sign);
+                workingPublicKey = sign.getPublicKey();
+            }
+            else
+            {
+                workingIssuerName = PrincipalUtils.getCA(trust);
+                workingPublicKey = trust.getCAPublicKey();
+            }
+        }
+        catch (RuntimeException ex)
+        {
+            throw new ExtCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath,
+                    -1);
+        }
+
+        AlgorithmIdentifier workingAlgId = null;
+        try
+        {
+            workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
+        }
+        catch (CertPathValidatorException e)
+        {
+            throw new ExtCertPathValidatorException(
+                    "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1);
+        }
+        ASN1ObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.getAlgorithm();
+        ASN1Encodable workingPublicKeyParameters = workingAlgId.getParameters();
+
+        //
+        // (k)
+        //
+        int maxPathLength = n;
+
+        //
+        // 6.1.3
+        //
+
+        if (paramsPKIX.getTargetConstraints() != null
+                && !paramsPKIX.getTargetConstraints().match((X509Certificate) certs.get(0)))
+        {
+            throw new ExtCertPathValidatorException(
+                    "Target certificate in certification path does not match targetConstraints.", null, certPath, 0);
+        }
+
+        // 
+        // initialize CertPathChecker's
+        //
+
+
+        X509Certificate cert = null;
+
+        for (index = certs.size() - 1; index >= 0; index--)
+        {
+            // try
+            // {
+            //
+            // i as defined in the algorithm description
+            //
+            i = n - index;
+
+            //
+            // set certificate to be checked in this round
+            // sign and workingPublicKey and workingIssuerName are set
+            // at the end of the for loop and initialized the
+            // first time from the TrustAnchor
+            //
+            cert = (X509Certificate) certs.get(index);
+            boolean verificationAlreadyPerformed = (index == certs.size() - 1);
+
+            try
+            {
+                checkCertificate(cert);
+            }
+            catch (AnnotatedException e)
+            {
+                throw new CertPathValidatorException(e.getMessage(), e.getUnderlyingException(), certPath, index);
+            }
+
+            //
+            // 6.1.3
+            //
+
+            RFC3280CertPathUtilities.processCertA(certPath, paramsPKIX, validityDate, revocationChecker, index,
+                workingPublicKey, verificationAlreadyPerformed, workingIssuerName, sign);
+
+            RFC3280CertPathUtilities.processCertBC(certPath, index, nameConstraintValidator, isForCRLCheck);
+
+            validPolicyTree = RFC3280CertPathUtilities.processCertD(certPath, index, acceptablePolicies,
+                    validPolicyTree, policyNodes, inhibitAnyPolicy, isForCRLCheck);
+
+            validPolicyTree = RFC3280CertPathUtilities.processCertE(certPath, index, validPolicyTree);
+
+            RFC3280CertPathUtilities.processCertF(certPath, index, validPolicyTree, explicitPolicy);
+
+            //
+            // 6.1.4
+            //
+            if (i != n)
+            {
+                if (cert != null && cert.getVersion() == 1)
+                {
+                    // we've found the trust anchor at the top of the path, ignore and keep going
+                    if ((i == 1) && cert.equals(trust.getTrustedCert()))
+                    {
+                        continue;
+                    }
+                    throw new CertPathValidatorException("Version 1 certificates can't be used as CA ones.", null,
+                            certPath, index);
+                }
+
+                RFC3280CertPathUtilities.prepareNextCertA(certPath, index);
+
+                validPolicyTree = RFC3280CertPathUtilities.prepareCertB(certPath, index, policyNodes, validPolicyTree,
+                        policyMapping);
+
+                RFC3280CertPathUtilities.prepareNextCertG(certPath, index, nameConstraintValidator);
+
+                // (h)
+                explicitPolicy = RFC3280CertPathUtilities.prepareNextCertH1(certPath, index, explicitPolicy);
+                policyMapping = RFC3280CertPathUtilities.prepareNextCertH2(certPath, index, policyMapping);
+                inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertH3(certPath, index, inhibitAnyPolicy);
+
+                //
+                // (i)
+                //
+                explicitPolicy = RFC3280CertPathUtilities.prepareNextCertI1(certPath, index, explicitPolicy);
+                policyMapping = RFC3280CertPathUtilities.prepareNextCertI2(certPath, index, policyMapping);
+
+                // (j)
+                inhibitAnyPolicy = RFC3280CertPathUtilities.prepareNextCertJ(certPath, index, inhibitAnyPolicy);
+
+                // (k)
+                RFC3280CertPathUtilities.prepareNextCertK(certPath, index);
+
+                // (l)
+                maxPathLength = RFC3280CertPathUtilities.prepareNextCertL(certPath, index, maxPathLength);
+
+                // (m)
+                maxPathLength = RFC3280CertPathUtilities.prepareNextCertM(certPath, index, maxPathLength);
+
+                // (n)
+                RFC3280CertPathUtilities.prepareNextCertN(certPath, index);
+
+                Set criticalExtensions = cert.getCriticalExtensionOIDs();
+                if (criticalExtensions != null)
+                {
+                    criticalExtensions = new HashSet(criticalExtensions);
+
+                    // these extensions are handled by the algorithm
+                    criticalExtensions.remove(RFC3280CertPathUtilities.KEY_USAGE);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.CERTIFICATE_POLICIES);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_MAPPINGS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_CONSTRAINTS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.BASIC_CONSTRAINTS);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME);
+                    criticalExtensions.remove(RFC3280CertPathUtilities.NAME_CONSTRAINTS);
+                }
+                else
+                {
+                    criticalExtensions = new HashSet();
+                }
+
+                // (o)
+                RFC3280CertPathUtilities.prepareNextCertO(certPath, index, criticalExtensions, pathCheckers);
+                
+                // set signing certificate for next round
+                sign = cert;
+
+                // (c)
+                workingIssuerName = PrincipalUtils.getSubjectPrincipal(sign);
+
+                // (d)
+                try
+                {
+                    workingPublicKey = CertPathValidatorUtilities.getNextWorkingKey(certPath.getCertificates(), index, helper);
+                }
+                catch (CertPathValidatorException e)
+                {
+                    throw new CertPathValidatorException("Next working key could not be retrieved.", e, certPath, index);
+                }
+
+                workingAlgId = CertPathValidatorUtilities.getAlgorithmIdentifier(workingPublicKey);
+                // (f)
+                workingPublicKeyAlgorithm = workingAlgId.getAlgorithm();
+                // (e)
+                workingPublicKeyParameters = workingAlgId.getParameters();
+            }
+        }
+
+        //
+        // 6.1.5 Wrap-up procedure
+        //
+
+        explicitPolicy = RFC3280CertPathUtilities.wrapupCertA(explicitPolicy, cert);
+
+        explicitPolicy = RFC3280CertPathUtilities.wrapupCertB(certPath, index + 1, explicitPolicy);
+
+        //
+        // (c) (d) and (e) are already done
+        //
+
+        //
+        // (f)
+        //
+        Set criticalExtensions = cert.getCriticalExtensionOIDs();
+
+        if (criticalExtensions != null)
+        {
+            criticalExtensions = new HashSet(criticalExtensions);
+            // these extensions are handled by the algorithm
+            criticalExtensions.remove(RFC3280CertPathUtilities.KEY_USAGE);
+            criticalExtensions.remove(RFC3280CertPathUtilities.CERTIFICATE_POLICIES);
+            criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_MAPPINGS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY);
+            criticalExtensions.remove(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT);
+            criticalExtensions.remove(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR);
+            criticalExtensions.remove(RFC3280CertPathUtilities.POLICY_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.BASIC_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME);
+            criticalExtensions.remove(RFC3280CertPathUtilities.NAME_CONSTRAINTS);
+            criticalExtensions.remove(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS);
+            criticalExtensions.remove(Extension.extendedKeyUsage.getId());
+        }
+        else
+        {
+            criticalExtensions = new HashSet();
+        }
+
+        RFC3280CertPathUtilities.wrapupCertF(certPath, index + 1, pathCheckers, criticalExtensions);
+
+        PKIXPolicyNode intersection = RFC3280CertPathUtilities.wrapupCertG(certPath, paramsPKIX, userInitialPolicySet,
+                index + 1, policyNodes, validPolicyTree, acceptablePolicies);
+
+        if ((explicitPolicy > 0) || (intersection != null))
+        {
+            return new PKIXCertPathValidatorResult(trust, intersection, cert.getPublicKey());
+        }
+
+        throw new CertPathValidatorException("Path processing failed on policy.", null, certPath, index);
+    }
+
+    static void checkCertificate(X509Certificate cert)
+        throws AnnotatedException
+    {
+        if (cert instanceof BCX509Certificate)
+        {
+            RuntimeException cause = null;
+            try
+            {
+                if (null != ((BCX509Certificate)cert).getTBSCertificateNative())
+                {
+                    return;
+                }
+            }
+            catch (RuntimeException e)
+            {
+                cause = e;
+            }
+
+            throw new AnnotatedException("unable to process TBSCertificate", cause);
+        }
+
+        try
+        {
+            TBSCertificate.getInstance(cert.getTBSCertificate());
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new AnnotatedException("unable to process TBSCertificate", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            throw new AnnotatedException(e.getMessage());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvCrlRevocationChecker.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvCrlRevocationChecker.java
new file mode 100644
index 0000000..6f62483
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvCrlRevocationChecker.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+
+class ProvCrlRevocationChecker
+    implements PKIXCertRevocationChecker
+{
+    private final JcaJceHelper helper;
+
+    private PKIXCertRevocationCheckerParameters params;
+    private Date currentDate = null;
+
+    public ProvCrlRevocationChecker(JcaJceHelper helper)
+    {
+        this.helper = helper;
+    }
+
+    public void setParameter(String name, Object value)
+    {
+
+    }
+
+    public void initialize(PKIXCertRevocationCheckerParameters params)
+    {
+        this.params = params;
+        this.currentDate = new Date();
+    }
+
+    public void init(boolean forForward)
+        throws CertPathValidatorException
+    {
+        if (forForward)
+        {
+            throw new CertPathValidatorException("forward checking not supported");
+        }
+
+        this.params = null;
+        this.currentDate = new Date();
+    }
+
+    public void check(Certificate certificate)
+        throws CertPathValidatorException
+    {
+        try
+        {
+            RFC3280CertPathUtilities.checkCRLs(params, params.getParamsPKIX(), currentDate, params.getValidDate(),
+                (X509Certificate)certificate, params.getSigningCert(), params.getWorkingPublicKey(),
+                params.getCertPath().getCertificates(), helper);
+        }
+        catch (AnnotatedException e)
+        {
+            Throwable cause = e;
+            if (null != e.getCause())
+            {
+                cause = e.getCause();
+            }
+            throw new CertPathValidatorException(e.getMessage(), cause, params.getCertPath(), params.getIndex());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvOcspRevocationChecker.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvOcspRevocationChecker.java
new file mode 100644
index 0000000..b1e7c8c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvOcspRevocationChecker.java
@@ -0,0 +1,619 @@
+package org.bouncycastle.jce.provider;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.Extension;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1String;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.bsi.BSIObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
+import org.bouncycastle.asn1.ocsp.CertID;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.ocsp.ResponseBytes;
+import org.bouncycastle.asn1.ocsp.ResponseData;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.ocsp.SingleResponse;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.RSASSAPSSparams;
+import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStrictStyle;
+import org.bouncycastle.asn1.x509.AccessDescription;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+import org.bouncycastle.jcajce.util.MessageDigestUtils;
+import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Properties;
+
+class ProvOcspRevocationChecker
+    implements PKIXCertRevocationChecker
+{
+    private static final int DEFAULT_OCSP_TIMEOUT = 15000;
+    private static final int DEFAULT_OCSP_MAX_RESPONSE_SIZE = 32 * 1024;
+
+    private static final Map oids = new HashMap();
+
+    static
+    {
+        //
+        // reverse mappings
+        //
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410");
+        oids.put(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256, "GOST3411-2012-256WITHECGOST3410-2012-256");
+        oids.put(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512, "GOST3411-2012-512WITHECGOST3410-2012-512");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA1, "SHA1WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA224, "SHA224WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA256, "SHA256WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA384, "SHA384WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA512, "SHA512WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_RIPEMD160, "RIPEMD160WITHPLAIN-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512WITHCVC-ECDSA");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmss, "XMSS");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT");
+
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA");
+        oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+        oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA");
+    }
+
+    private final ProvRevocationChecker parent;
+    private final JcaJceHelper helper;
+
+    private PKIXCertRevocationCheckerParameters parameters;
+    private boolean isEnabledOCSP;
+    private String ocspURL;
+
+    public ProvOcspRevocationChecker(ProvRevocationChecker parent, JcaJceHelper helper)
+    {
+        this.parent = parent;
+        this.helper = helper;
+    }
+
+    public void setParameter(String name, Object value)
+    {
+
+    }
+
+    public void initialize(PKIXCertRevocationCheckerParameters parameters)
+    {
+        this.parameters = parameters;
+        this.isEnabledOCSP = Properties.isOverrideSet("ocsp.enable");
+        this.ocspURL = Properties.getPropertyValue("ocsp.responderURL");
+    }
+
+    public List<CertPathValidatorException> getSoftFailExceptions()
+    {
+        return null;
+    }
+
+    public void init(boolean forForward)
+        throws CertPathValidatorException
+    {
+        if (forForward)
+        {
+            throw new CertPathValidatorException("forward checking not supported");
+        }
+
+        this.parameters = null;
+        this.isEnabledOCSP = Properties.isOverrideSet("ocsp.enable");
+        this.ocspURL = Properties.getPropertyValue("ocsp.responderURL");
+    }
+
+    public boolean isForwardCheckingSupported()
+    {
+        return false;
+    }
+
+    public Set<String> getSupportedExtensions()
+    {
+        return null;
+    }
+
+    public void check(Certificate certificate)
+        throws CertPathValidatorException
+    {
+        X509Certificate cert = (X509Certificate)certificate;
+        Map<X509Certificate, byte[]> ocspResponses = parent.getOcspResponses();
+        URI ocspUri = parent.getOcspResponder();
+
+        if (ocspUri == null)
+        {
+            if (this.ocspURL != null)
+            {
+                try
+                {
+                    ocspUri = new URI(this.ocspURL);
+                }
+                catch (URISyntaxException e)
+                {
+                    throw new CertPathValidatorException("configuration error: " + e.getMessage(),
+                        e, parameters.getCertPath(), parameters.getIndex());
+                }
+            }
+            else
+            {
+                ocspUri = getOcspResponderURI(cert);
+            }
+        }
+
+        byte[] nonce = null;
+        boolean preValidated = false;
+        if (ocspResponses.get(cert) == null && ocspUri != null)
+        {
+            // if we're here we need to make a network access, if we haven't been given a URL explicitly block it.
+            if (ocspURL == null
+                && parent.getOcspResponder() == null
+                && !isEnabledOCSP)
+            {
+                throw new RecoverableCertPathValidatorException("OCSP disabled by \"ocsp.enable\" setting",
+                                    null, parameters.getCertPath(), parameters.getIndex());
+            }
+
+            org.bouncycastle.asn1.x509.Certificate issuer = extractCert();
+
+            // TODO: configure hash algorithm
+            CertID id = createCertID(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1), issuer, new ASN1Integer(cert.getSerialNumber()));
+
+            OCSPResponse response = OcspCache.getOcspResponse(id, parameters, ocspUri, parent.getOcspResponderCert(), parent.getOcspExtensions(), helper);
+
+            try
+            {
+                ocspResponses.put(cert, response.getEncoded());
+                preValidated = true;
+            }
+            catch (IOException e)
+            {
+                throw new CertPathValidatorException(
+                          "unable to encode OCSP response", e, parameters.getCertPath(), parameters.getIndex());
+            }
+        }
+        else
+        {
+            List exts = parent.getOcspExtensions();
+            for (int i = 0; i != exts.size(); i++)
+            {
+                Extension ext = (Extension)exts.get(i);
+                byte[] value = ext.getValue();
+
+                if (OCSPObjectIdentifiers.id_pkix_ocsp_nonce.getId().equals(ext.getId()))
+                {
+                    nonce = value;
+                }
+            }
+        }
+
+        if (!ocspResponses.isEmpty())
+        {
+            OCSPResponse ocspResponse = OCSPResponse.getInstance(ocspResponses.get(cert));
+            ASN1Integer serialNumber = new ASN1Integer(cert.getSerialNumber());
+
+            if (ocspResponse != null)
+            {
+                if (OCSPResponseStatus.SUCCESSFUL == ocspResponse.getResponseStatus().getIntValue())
+                {
+                    ResponseBytes respBytes = ResponseBytes.getInstance(ocspResponse.getResponseBytes());
+
+                    if (respBytes.getResponseType().equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic))
+                    {
+                        try
+                        {
+                            BasicOCSPResponse basicResp = BasicOCSPResponse.getInstance(respBytes.getResponse().getOctets());
+
+                            if (preValidated || validatedOcspResponse(basicResp, parameters, nonce, parent.getOcspResponderCert(), helper))
+                            {
+                                ResponseData responseData = ResponseData.getInstance(basicResp.getTbsResponseData());
+
+                                ASN1Sequence s = responseData.getResponses();
+
+                                CertID certID = null;
+                                for (int i = 0; i != s.size(); i++)
+                                {
+                                    SingleResponse resp = SingleResponse.getInstance(s.getObjectAt(i));
+
+                                    if (serialNumber.equals(resp.getCertID().getSerialNumber()))
+                                    {
+                                        ASN1GeneralizedTime nextUp = resp.getNextUpdate();
+                                        if (nextUp != null && parameters.getValidDate().after(nextUp.getDate()))
+                                        {
+                                            throw new ExtCertPathValidatorException("OCSP response expired");
+                                        }
+                                        if (certID == null || !certID.getHashAlgorithm().equals(resp.getCertID().getHashAlgorithm()))
+                                        {
+                                            org.bouncycastle.asn1.x509.Certificate issuer = extractCert();
+
+                                            certID = createCertID(resp.getCertID(), issuer, serialNumber);
+                                        }
+                                        if (certID.equals(resp.getCertID()))
+                                        {
+                                            if (resp.getCertStatus().getTagNo() == 0)
+                                            {
+                                                // we're good!
+                                                return;
+                                            }
+                                            if (resp.getCertStatus().getTagNo() == 1)
+                                            {
+                                                RevokedInfo info = RevokedInfo.getInstance(resp.getCertStatus().getStatus());
+                                                CRLReason reason = info.getRevocationReason();
+                                                throw new CertPathValidatorException(
+                                                    "certificate revoked, reason=(" + reason + "), date=" + info.getRevocationTime().getDate(),
+                                                    null, parameters.getCertPath(), parameters.getIndex());
+                                            }
+                                            throw new CertPathValidatorException(
+                                                "certificate revoked, details unknown",
+                                                null, parameters.getCertPath(), parameters.getIndex());
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        catch (CertPathValidatorException e)
+                        {
+                            throw e;
+                        }
+                        catch (Exception e)
+                        {
+                            throw new CertPathValidatorException(
+                                "unable to process OCSP response", e, parameters.getCertPath(), parameters.getIndex());
+                        }
+                    }
+                }
+                else
+                {
+                    throw new CertPathValidatorException(
+                        "OCSP response failed: " + ocspResponse.getResponseStatus().getValue(),
+                        null, parameters.getCertPath(), parameters.getIndex());
+                }
+            }
+            else
+            {
+                // TODO: add checking for the OCSP extension (properly vetted)
+                throw new RecoverableCertPathValidatorException(
+                    "no OCSP response found for certificate", null, parameters.getCertPath(), parameters.getIndex());
+            }
+        }
+        else
+        {
+            throw new RecoverableCertPathValidatorException(
+                "no OCSP response found for any certificate", null, parameters.getCertPath(), parameters.getIndex());
+        }
+    }
+
+    static URI getOcspResponderURI(X509Certificate cert)
+    {
+        byte[] extValue = cert.getExtensionValue(org.bouncycastle.asn1.x509.Extension.authorityInfoAccess.getId());
+        if (extValue == null)
+        {
+            return null;
+        }
+        else
+        {
+            AuthorityInformationAccess aiAccess = AuthorityInformationAccess.getInstance(
+                ASN1OctetString.getInstance(extValue).getOctets());
+
+            AccessDescription[] descriptions = aiAccess.getAccessDescriptions();
+            for (int i = 0; i != descriptions.length; i++)
+            {
+                AccessDescription aDesc = descriptions[i];
+                if (AccessDescription.id_ad_ocsp.equals(aDesc.getAccessMethod()))
+                {
+                    GeneralName name = aDesc.getAccessLocation();
+                    if (name.getTagNo() == GeneralName.uniformResourceIdentifier)
+                    {
+                        try
+                        {
+                            return new URI(((ASN1String)name.getName()).getString());
+                        }
+                        catch (URISyntaxException e)
+                        {
+                            // ignore...
+                        }
+                    }
+                }
+            }
+
+            return null;
+        }
+    }
+
+    static boolean validatedOcspResponse(BasicOCSPResponse basicResp, PKIXCertRevocationCheckerParameters parameters, byte[] nonce, X509Certificate responderCert, JcaJceHelper helper)
+        throws CertPathValidatorException
+    {
+        try
+        {
+            ASN1Sequence certs = basicResp.getCerts();
+
+            Signature sig = helper.createSignature(getSignatureName(basicResp.getSignatureAlgorithm()));
+
+            X509Certificate sigCert = getSignerCert(basicResp, parameters.getSigningCert(), responderCert, helper);
+            if (sigCert == null && certs == null)
+            {
+                throw new CertPathValidatorException("OCSP responder certificate not found");
+            }
+
+            if (sigCert != null)
+            {
+                sig.initVerify(sigCert.getPublicKey());
+            }
+            else
+            {
+                CertificateFactory cf = helper.createCertificateFactory("X.509");
+
+                X509Certificate ocspCert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(certs.getObjectAt(0).toASN1Primitive().getEncoded()));
+
+                // check cert signed by CA
+                ocspCert.verify(parameters.getSigningCert().getPublicKey());
+
+                // check cert valid
+                ocspCert.checkValidity(parameters.getValidDate());
+
+                // check ID
+                if (!responderMatches(basicResp.getTbsResponseData().getResponderID(), ocspCert, helper))
+                {
+                    throw new CertPathValidatorException("responder certificate does not match responderID", null,
+                        parameters.getCertPath(), parameters.getIndex());
+                }
+
+                // TODO: RFC 6960 allows for a "no check" extension - where present it means the CA says the cert
+                // will remain valid for it's lifetime. If any caching is added here that should be taken into account.
+
+                // check we are valid
+                List extendedKeyUsage = ocspCert.getExtendedKeyUsage();
+                if (extendedKeyUsage == null || !extendedKeyUsage.contains(KeyPurposeId.id_kp_OCSPSigning.getId()))
+                {
+                    throw new CertPathValidatorException("responder certificate not valid for signing OCSP responses", null,
+                        parameters.getCertPath(), parameters.getIndex());
+                }
+
+                sig.initVerify(ocspCert);
+            }
+
+            sig.update(basicResp.getTbsResponseData().getEncoded(ASN1Encoding.DER));
+
+            if (sig.verify(basicResp.getSignature().getBytes()))
+            {
+                if (nonce != null)
+                {
+                    Extensions exts = basicResp.getTbsResponseData().getResponseExtensions();
+
+                    org.bouncycastle.asn1.x509.Extension ext = exts.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+
+                    if (!Arrays.areEqual(nonce, ext.getExtnValue().getOctets()))
+                    {
+                        throw new CertPathValidatorException("nonce mismatch in OCSP response", null, parameters.getCertPath(), parameters.getIndex());
+                    }
+                }
+                return true;
+            }
+
+            return false;
+        }
+        catch (CertPathValidatorException e)
+        {
+            throw e;
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CertPathValidatorException("OCSP response failure: " + e.getMessage(), e, parameters.getCertPath(), parameters.getIndex());
+        }
+        catch (IOException e)
+        {
+            throw new CertPathValidatorException("OCSP response failure: " + e.getMessage(), e, parameters.getCertPath(), parameters.getIndex());
+        }
+    }
+
+    private static X509Certificate getSignerCert(BasicOCSPResponse basicResp, X509Certificate signingCert, X509Certificate responderCert, JcaJceHelper helper)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        ResponderID responderID = basicResp.getTbsResponseData().getResponderID();
+
+        byte[] keyHash = responderID.getKeyHash();
+        if (keyHash != null)
+        {
+            MessageDigest digest = helper.createMessageDigest("SHA1");
+            X509Certificate sigCert = responderCert;
+
+            if (sigCert != null)
+            {
+                if (Arrays.areEqual(keyHash, calcKeyHash(digest, sigCert.getPublicKey())))
+                {
+                    return sigCert;
+                }
+            }
+
+            sigCert = signingCert;
+            if (sigCert != null)
+            {
+                if (Arrays.areEqual(keyHash, calcKeyHash(digest, sigCert.getPublicKey())))
+                {
+                    return sigCert;
+                }
+            }
+        }
+        else
+        {
+            X500Name name = X500Name.getInstance(BCStrictStyle.INSTANCE, responderID.getName());
+            X509Certificate sigCert = responderCert;
+
+            if (sigCert != null)
+            {
+                if (name.equals(X500Name.getInstance(BCStrictStyle.INSTANCE, sigCert.getSubjectX500Principal().getEncoded())))
+                {
+                    return sigCert;
+                }
+            }
+
+            sigCert = signingCert;
+            if (sigCert != null)
+            {
+                if (name.equals(X500Name.getInstance(BCStrictStyle.INSTANCE, sigCert.getSubjectX500Principal().getEncoded())))
+                {
+                    return sigCert;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean responderMatches(ResponderID responderID, X509Certificate certificate, JcaJceHelper helper)
+        throws NoSuchProviderException, NoSuchAlgorithmException
+    {
+        byte[] keyHash = responderID.getKeyHash();
+        if (keyHash != null)
+        {
+            MessageDigest digest = helper.createMessageDigest("SHA1");
+
+            return Arrays.areEqual(keyHash, calcKeyHash(digest, certificate.getPublicKey()));
+        }
+        else
+        {
+            X500Name name = X500Name.getInstance(BCStrictStyle.INSTANCE, responderID.getName());
+
+            return name.equals(X500Name.getInstance(BCStrictStyle.INSTANCE, certificate.getSubjectX500Principal().getEncoded()));
+        }
+    }
+
+    private static byte[] calcKeyHash(MessageDigest digest, PublicKey key)
+    {
+        SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(key.getEncoded());
+
+        return digest.digest(info.getPublicKeyData().getBytes());
+    }
+
+    private org.bouncycastle.asn1.x509.Certificate extractCert()
+        throws CertPathValidatorException
+    {
+        try
+        {
+            return org.bouncycastle.asn1.x509.Certificate.getInstance(parameters.getSigningCert().getEncoded());
+        }
+        catch (Exception e)
+        {
+            throw new CertPathValidatorException("cannot process signing cert: " + e.getMessage(), e, parameters.getCertPath(), parameters.getIndex());
+        }
+    }
+
+    private CertID createCertID(CertID base, org.bouncycastle.asn1.x509.Certificate issuer, ASN1Integer serialNumber)
+        throws CertPathValidatorException
+    {
+        return createCertID(base.getHashAlgorithm(), issuer, serialNumber);
+    }
+
+    private CertID createCertID(AlgorithmIdentifier digestAlg, org.bouncycastle.asn1.x509.Certificate issuer, ASN1Integer serialNumber)
+        throws CertPathValidatorException
+    {
+        try
+        {
+            MessageDigest digest = helper.createMessageDigest(MessageDigestUtils.getDigestName(digestAlg.getAlgorithm()));
+
+            ASN1OctetString issuerNameHash = new DEROctetString(digest.digest(issuer.getSubject().getEncoded(ASN1Encoding.DER)));
+
+            ASN1OctetString issuerKeyHash = new DEROctetString(digest.digest(
+                issuer.getSubjectPublicKeyInfo().getPublicKeyData().getBytes()));
+
+            return new CertID(digestAlg, issuerNameHash, issuerKeyHash, serialNumber);
+        }
+        catch (Exception e)
+        {
+            throw new CertPathValidatorException("problem creating ID: " + e, e);
+        }
+    }
+
+    // we need to remove the - to create a correct signature name
+    private static String getDigestName(ASN1ObjectIdentifier oid)
+    {
+        String name = MessageDigestUtils.getDigestName(oid);
+
+        int dIndex = name.indexOf('-');
+        if (dIndex > 0 && !name.startsWith("SHA3"))
+        {
+            return name.substring(0, dIndex) + name.substring(dIndex + 1);
+        }
+
+        return name;
+    }
+
+    private static String getSignatureName(
+        AlgorithmIdentifier sigAlgId)
+    {
+        ASN1Encodable params = sigAlgId.getParameters();
+
+        if (params != null && !DERNull.INSTANCE.equals(params))
+        {
+            if (sigAlgId.getAlgorithm().equals(PKCSObjectIdentifiers.id_RSASSA_PSS))
+            {
+                RSASSAPSSparams rsaParams = RSASSAPSSparams.getInstance(params);
+                return getDigestName(rsaParams.getHashAlgorithm().getAlgorithm()) + "WITHRSAANDMGF1";
+            }
+        }
+
+        if (oids.containsKey(sigAlgId.getAlgorithm()))
+        {
+            return (String)oids.get(sigAlgId.getAlgorithm());
+        }
+
+        return sigAlgId.getAlgorithm().getId();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvRevocationChecker.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvRevocationChecker.java
new file mode 100644
index 0000000..673d887
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/ProvRevocationChecker.java
@@ -0,0 +1,179 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.bsi.BSIObjectIdentifiers;
+import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.eac.EACObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.rosstandart.RosstandartObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
+import org.bouncycastle.jcajce.util.JcaJceHelper;
+
+class ProvRevocationChecker
+    extends PKIXRevocationChecker
+    implements PKIXCertRevocationChecker
+{
+    private static final int DEFAULT_OCSP_TIMEOUT = 15000;
+    private static final int DEFAULT_OCSP_MAX_RESPONSE_SIZE = 32 * 1024;
+
+    private static final Map oids = new HashMap();
+
+    static
+    {
+        //
+        // reverse mappings
+        //
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), "SHA1WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha224WithRSAEncryption, "SHA224WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha256WithRSAEncryption, "SHA256WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha384WithRSAEncryption, "SHA384WITHRSA");
+        oids.put(PKCSObjectIdentifiers.sha512WithRSAEncryption, "SHA512WITHRSA");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_94, "GOST3411WITHGOST3410");
+        oids.put(CryptoProObjectIdentifiers.gostR3411_94_with_gostR3410_2001, "GOST3411WITHECGOST3410");
+        oids.put(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256, "GOST3411-2012-256WITHECGOST3410-2012-256");
+        oids.put(RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512, "GOST3411-2012-512WITHECGOST3410-2012-512");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA1, "SHA1WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA224, "SHA224WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA256, "SHA256WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA384, "SHA384WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_SHA512, "SHA512WITHPLAIN-ECDSA");
+        oids.put(BSIObjectIdentifiers.ecdsa_plain_RIPEMD160, "RIPEMD160WITHPLAIN-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384WITHCVC-ECDSA");
+        oids.put(EACObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512WITHCVC-ECDSA");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmss, "XMSS");
+        oids.put(IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT");
+
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.4"), "MD5WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.113549.1.1.2"), "MD2WITHRSA");
+        oids.put(new ASN1ObjectIdentifier("1.2.840.10040.4.3"), "SHA1WITHDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA1, "SHA1WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA224, "SHA224WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA256, "SHA256WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA384, "SHA384WITHECDSA");
+        oids.put(X9ObjectIdentifiers.ecdsa_with_SHA512, "SHA512WITHECDSA");
+        oids.put(OIWObjectIdentifiers.sha1WithRSA, "SHA1WITHRSA");
+        oids.put(OIWObjectIdentifiers.dsaWithSHA1, "SHA1WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha224, "SHA224WITHDSA");
+        oids.put(NISTObjectIdentifiers.dsa_with_sha256, "SHA256WITHDSA");
+    }
+
+    private final JcaJceHelper helper;
+    private final ProvCrlRevocationChecker crlChecker;
+    private final ProvOcspRevocationChecker ocspChecker;
+
+    private PKIXCertRevocationCheckerParameters parameters;
+
+    public ProvRevocationChecker(JcaJceHelper helper)
+    {
+        this.helper = helper;
+        this.crlChecker = new ProvCrlRevocationChecker(helper);
+        this.ocspChecker = new ProvOcspRevocationChecker(this, helper);
+    }
+
+    public void setParameter(String name, Object value)
+    {
+
+    }
+
+    public void initialize(PKIXCertRevocationCheckerParameters parameters)
+    {
+        this.parameters = parameters;
+        crlChecker.initialize(parameters);
+        ocspChecker.initialize(parameters);
+    }
+
+    public List<CertPathValidatorException> getSoftFailExceptions()
+    {
+        return ocspChecker.getSoftFailExceptions();
+    }
+
+    public void init(boolean forForward)
+        throws CertPathValidatorException
+    {
+        this.parameters = null;
+         crlChecker.init(forForward);
+         ocspChecker.init(forForward);
+    }
+
+    public boolean isForwardCheckingSupported()
+    {
+        return false;
+    }
+
+    public Set<String> getSupportedExtensions()
+    {
+        return null;
+    }
+
+    public void check(Certificate certificate, Collection<String> collection)
+        throws CertPathValidatorException
+    {
+        X509Certificate cert = (X509Certificate)certificate;
+
+        // only check end-entity certificates.
+        if (hasOption(Option.ONLY_END_ENTITY) && cert.getBasicConstraints() != -1)
+        {
+            return;
+        }
+
+        if (hasOption(Option.PREFER_CRLS))
+        {
+            try
+            {
+                crlChecker.check(certificate);
+            }
+            catch (RecoverableCertPathValidatorException e)
+            {
+                if (!hasOption(Option.NO_FALLBACK))
+                {
+                    ocspChecker.check(certificate);
+                }
+                else
+                {
+                    throw e;
+                }
+            }
+        }
+        else
+        {
+            try
+            {
+                ocspChecker.check(certificate);
+            }
+            catch (RecoverableCertPathValidatorException e)
+            {
+                if (!hasOption(Option.NO_FALLBACK))
+                {
+                    crlChecker.check(certificate);
+                }
+                else
+                {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    private boolean hasOption(PKIXRevocationChecker.Option option)
+    {
+        return this.getOptions().contains(option);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
index 9ca500b..fb2d4c0 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
@@ -6,23 +6,23 @@
 import java.security.PublicKey;
 import java.security.cert.CertPath;
 import java.security.cert.CertPathBuilderException;
+import java.security.cert.CertPathBuilderSpi;
 import java.security.cert.CertPathValidatorException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.PKIXCertPathChecker;
 import java.security.cert.X509CRL;
-import java.security.cert.X509CRLSelector;
 import java.security.cert.X509CertSelector;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Extension;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -53,17 +53,19 @@
 import org.bouncycastle.asn1.x509.NameConstraints;
 import org.bouncycastle.asn1.x509.PolicyInformation;
 import org.bouncycastle.jcajce.PKIXCRLStore;
-import org.bouncycastle.jcajce.PKIXCRLStoreSelector;
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
 import org.bouncycastle.jcajce.PKIXCertStoreSelector;
 import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
 import org.bouncycastle.jcajce.PKIXExtendedParameters;
+import org.bouncycastle.jcajce.provider.symmetric.util.ClassUtil;
 import org.bouncycastle.jcajce.util.JcaJceHelper;
 import org.bouncycastle.jce.exception.ExtCertPathValidatorException;
 import org.bouncycastle.util.Arrays;
 
 class RFC3280CertPathUtilities
 {
-    private static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
+    private static final Class revChkClass = ClassUtil.loadClass(RFC3280CertPathUtilities.class, "java.security.cert.PKIXRevocationChecker");
 
     /**
      * If the complete CRL includes an issuing distribution point (IDP) CRL
@@ -170,8 +172,7 @@
                             genNames = new GeneralName[1];
                             try
                             {
-                                genNames[0] = new GeneralName(X500Name.getInstance(PrincipalUtils
-                                    .getEncodedIssuerPrincipal(cert).getEncoded()));
+                                genNames[0] = new GeneralName(PrincipalUtils.getEncodedIssuerPrincipal(cert));
                             }
                             catch (Exception e)
                             {
@@ -469,11 +470,11 @@
         PKIXCertStoreSelector selector = new PKIXCertStoreSelector.Builder(certSelector).build();
 
         // get CRL signing certs
-        Collection coll;
+        LinkedHashSet coll = new LinkedHashSet();
         try
         {
-            coll = CertPathValidatorUtilities.findCertificates(selector, paramsPKIX.getCertificateStores());
-            coll.addAll(CertPathValidatorUtilities.findCertificates(selector, paramsPKIX.getCertStores()));
+            CertPathValidatorUtilities.findCertificates(coll, selector, paramsPKIX.getCertificateStores());
+            CertPathValidatorUtilities.findCertificates(coll, selector, paramsPKIX.getCertStores());
         }
         catch (AnnotatedException e)
         {
@@ -503,7 +504,8 @@
             }
             try
             {
-                PKIXCertPathBuilderSpi builder = new PKIXCertPathBuilderSpi(true);
+                CertPathBuilderSpi builder = (revChkClass != null)
+                                ? new PKIXCertPathBuilderSpi_8(true) : new PKIXCertPathBuilderSpi(true);
                 X509CertSelector tmpCertSelector = new X509CertSelector();
                 tmpCertSelector.setCertificate(signingCert);
 
@@ -554,9 +556,9 @@
         for (int i = 0; i < validCerts.size(); i++)
         {
             X509Certificate signCert = (X509Certificate)validCerts.get(i);
-            boolean[] keyusage = signCert.getKeyUsage();
+            boolean[] keyUsage = signCert.getKeyUsage();
 
-            if (keyusage != null && (keyusage.length < 7 || !keyusage[CRL_SIGN]))
+            if (keyUsage != null && (keyUsage.length <= CRL_SIGN || !keyUsage[CRL_SIGN]))
             {
                 lastException = new AnnotatedException(
                     "Issuer certificate key usage extension does not permit CRL signing.");
@@ -629,119 +631,6 @@
         return null;
     }
 
-    protected static Set processCRLA1i(
-        Date currentDate,
-        PKIXExtendedParameters paramsPKIX,
-        X509Certificate cert,
-        X509CRL crl)
-        throws AnnotatedException
-    {
-        Set set = new HashSet();
-        if (paramsPKIX.isUseDeltasEnabled())
-        {
-            CRLDistPoint freshestCRL = null;
-            try
-            {
-                freshestCRL = CRLDistPoint
-                    .getInstance(CertPathValidatorUtilities.getExtensionValue(cert, FRESHEST_CRL));
-            }
-            catch (AnnotatedException e)
-            {
-                throw new AnnotatedException("Freshest CRL extension could not be decoded from certificate.", e);
-            }
-            if (freshestCRL == null)
-            {
-                try
-                {
-                    freshestCRL = CRLDistPoint.getInstance(CertPathValidatorUtilities.getExtensionValue(crl,
-                        FRESHEST_CRL));
-                }
-                catch (AnnotatedException e)
-                {
-                    throw new AnnotatedException("Freshest CRL extension could not be decoded from CRL.", e);
-                }
-            }
-            if (freshestCRL != null)
-            {
-                List crlStores = new ArrayList();
-
-                crlStores.addAll(paramsPKIX.getCRLStores());
-
-                try
-                {
-                    crlStores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromCRLDistributionPoint(freshestCRL, paramsPKIX.getNamedCRLStoreMap()));
-                }
-                catch (AnnotatedException e)
-                {
-                    throw new AnnotatedException(
-                        "No new delta CRL locations could be added from Freshest CRL extension.", e);
-                }
-
-                // get delta CRL(s)
-                try
-                {
-                    set.addAll(CertPathValidatorUtilities.getDeltaCRLs(currentDate, crl, paramsPKIX.getCertStores(), crlStores));
-                }
-                catch (AnnotatedException e)
-                {
-                    throw new AnnotatedException("Exception obtaining delta CRLs.", e);
-                }
-            }
-        }
-        return set;
-    }
-
-    protected static Set[] processCRLA1ii(
-        Date currentDate,
-        PKIXExtendedParameters paramsPKIX,
-        X509Certificate cert,
-        X509CRL crl)
-        throws AnnotatedException
-    {
-        Set deltaSet = new HashSet();
-        X509CRLSelector crlselect = new X509CRLSelector();
-        crlselect.setCertificateChecking(cert);
-
-        try
-        {
-            crlselect.addIssuerName(PrincipalUtils.getIssuerPrincipal(crl).getEncoded());
-        }
-        catch (Exception e)
-        {
-            throw new AnnotatedException("Cannot extract issuer from CRL." + e, e);
-        }
-
-        PKIXCRLStoreSelector extSelect = new PKIXCRLStoreSelector.Builder(crlselect).setCompleteCRLEnabled(true).build();
-
-        Date validityDate = currentDate;
-
-        if (paramsPKIX.getDate() != null)
-        {
-            validityDate = paramsPKIX.getDate();
-        }
-
-        Set completeSet = CRL_UTIL.findCRLs(extSelect, validityDate, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
-
-        if (paramsPKIX.isUseDeltasEnabled())
-        {
-            // get delta CRL(s)
-            try
-            {
-                deltaSet.addAll(CertPathValidatorUtilities.getDeltaCRLs(validityDate, crl, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores()));
-            }
-            catch (AnnotatedException e)
-            {
-                throw new AnnotatedException("Exception obtaining delta CRLs.", e);
-            }
-        }
-        return new Set[]
-            {
-                completeSet,
-                deltaSet};
-    }
-
-
-
     /**
      * If use-deltas is set, verify the issuer and scope of the delta CRL.
      *
@@ -907,7 +796,7 @@
         ASN1Sequence pm = null;
         try
         {
-            pm = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            pm = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.POLICY_MAPPINGS));
         }
         catch (AnnotatedException ex)
@@ -1090,7 +979,7 @@
         ASN1Sequence pm = null;
         try
         {
-            pm = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            pm = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.POLICY_MAPPINGS));
         }
         catch (AnnotatedException ex)
@@ -1108,7 +997,7 @@
                 ASN1ObjectIdentifier subjectDomainPolicy = null;
                 try
                 {
-                    ASN1Sequence mapping = DERSequence.getInstance(mappings.getObjectAt(j));
+                    ASN1Sequence mapping = ASN1Sequence.getInstance(mappings.getObjectAt(j));
 
                     issuerDomainPolicy = ASN1ObjectIdentifier.getInstance(mapping.getObjectAt(0));
                     subjectDomainPolicy = ASN1ObjectIdentifier.getInstance(mapping.getObjectAt(1));
@@ -1165,7 +1054,7 @@
         ASN1Sequence certPolicies = null;
         try
         {
-            certPolicies = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            certPolicies = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.CERTIFICATE_POLICIES));
         }
         catch (AnnotatedException e)
@@ -1207,7 +1096,7 @@
 
             try
             {
-                dns = DERSequence.getInstance(principal.getEncoded());
+                dns = ASN1Sequence.getInstance(principal);
             }
             catch (Exception e)
             {
@@ -1306,7 +1195,7 @@
         ASN1Sequence certPolicies = null;
         try
         {
-            certPolicies = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            certPolicies = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.CERTIFICATE_POLICIES));
         }
         catch (AnnotatedException e)
@@ -1490,13 +1379,14 @@
     protected static void processCertA(
         CertPath certPath,
         PKIXExtendedParameters paramsPKIX,
+        Date validityDate,
+        PKIXCertRevocationChecker revocationChecker,
         int index,
         PublicKey workingPublicKey,
         boolean verificationAlreadyPerformed,
         X500Name workingIssuerName,
-        X509Certificate sign,
-        JcaJceHelper helper)
-        throws ExtCertPathValidatorException
+        X509Certificate sign)
+        throws CertPathValidatorException
     {
         List certs = certPath.getCertificates();
         X509Certificate cert = (X509Certificate)certs.get(index);
@@ -1518,12 +1408,22 @@
             }
         }
 
+        final Date validCertDate;
         try
         {
-            // (a) (2)
-            //
-            cert.checkValidity(CertPathValidatorUtilities
-                .getValidCertDateFromValidityModel(paramsPKIX, certPath, index));
+            validCertDate = CertPathValidatorUtilities.getValidCertDateFromValidityModel(validityDate,
+                paramsPKIX.getValidityModel(), certPath, index);
+        }
+        catch (AnnotatedException e)
+        {
+            throw new ExtCertPathValidatorException("Could not validate time of certificate.", e, certPath, index);
+        }
+
+        // (a) (2)
+        //
+        try
+        {
+            cert.checkValidity(validCertDate);
         }
         catch (CertificateExpiredException e)
         {
@@ -1533,40 +1433,26 @@
         {
             throw new ExtCertPathValidatorException("Could not validate certificate: " + e.getMessage(), e, certPath, index);
         }
-        catch (AnnotatedException e)
-        {
-            throw new ExtCertPathValidatorException("Could not validate time of certificate.", e, certPath, index);
-        }
 
         //
         // (a) (3)
         //
-        if (paramsPKIX.isRevocationEnabled())
+        if (revocationChecker != null)
         {
-            try
-            {
-                checkCRLs(paramsPKIX, cert, CertPathValidatorUtilities.getValidCertDateFromValidityModel(paramsPKIX,
-                    certPath, index), sign, workingPublicKey, certs, helper);
-            }
-            catch (AnnotatedException e)
-            {
-                Throwable cause = e;
-                if (null != e.getCause())
-                {
-                    cause = e.getCause();
-                }
-                throw new ExtCertPathValidatorException(e.getMessage(), cause, certPath, index);
-            }
+            revocationChecker.initialize(new PKIXCertRevocationCheckerParameters(paramsPKIX, validCertDate, certPath,
+                index, sign, workingPublicKey));
+
+            revocationChecker.check(cert);
         }
 
         //
         // (a) (4) name chaining
         //
-        if (!PrincipalUtils.getEncodedIssuerPrincipal(cert).equals(workingIssuerName))
+        X500Name issuer = PrincipalUtils.getIssuerPrincipal(cert);
+        if (!issuer.equals(workingIssuerName))
         {
-            throw new ExtCertPathValidatorException("IssuerName(" + PrincipalUtils.getEncodedIssuerPrincipal(cert)
-                + ") does not match SubjectName(" + workingIssuerName + ") of signing certificate.", null,
-                certPath, index);
+            throw new ExtCertPathValidatorException("IssuerName(" + issuer + ") does not match SubjectName("
+                + workingIssuerName + ") of signing certificate.", null, certPath, index);
         }
     }
 
@@ -1584,7 +1470,7 @@
         ASN1Sequence pc = null;
         try
         {
-            pc = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            pc = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.POLICY_CONSTRAINTS));
         }
         catch (Exception e)
@@ -1638,7 +1524,7 @@
         ASN1Sequence pc = null;
         try
         {
-            pc = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            pc = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.POLICY_CONSTRAINTS));
         }
         catch (Exception e)
@@ -1692,7 +1578,7 @@
         NameConstraints nc = null;
         try
         {
-            ASN1Sequence ncSeq = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            ASN1Sequence ncSeq = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.NAME_CONSTRAINTS));
             if (ncSeq != null)
             {
@@ -1745,38 +1631,52 @@
     }
 
     /**
-     * Checks a distribution point for revocation information for the
-     * certificate <code>cert</code>.
+     * Checks a distribution point for revocation information for the certificate <code>cert</code>.
      *
-     * @param dp                 The distribution point to consider.
-     * @param paramsPKIX         PKIX parameters.
-     * @param cert               Certificate to check if it is revoked.
-     * @param validDate          The date when the certificate revocation status should be
-     *                           checked.
-     * @param defaultCRLSignCert The issuer certificate of the certificate <code>cert</code>.
-     * @param defaultCRLSignKey  The public key of the issuer certificate
-     *                           <code>defaultCRLSignCert</code>.
-     * @param certStatus         The current certificate revocation status.
-     * @param reasonMask         The reasons mask which is already checked.
-     * @param certPathCerts      The certificates of the certification path.
-     * @throws AnnotatedException if the certificate is revoked or the status cannot be checked
-     *                            or some error occurs.
+     * @param dp
+     *            The distribution point to consider.
+     * @param paramsPKIX
+     *            PKIX parameters.
+     * @param currentDate
+     *            The date at which this check is being run.
+     * @param validityDate
+     *            The date when the certificate revocation status should be checked.
+     * @param cert
+     *            Certificate to check if it is revoked.
+     * @param defaultCRLSignCert
+     *            The issuer certificate of the certificate <code>cert</code>.
+     * @param defaultCRLSignKey
+     *            The public key of the issuer certificate <code>defaultCRLSignCert</code>.
+     * @param certStatus
+     *            The current certificate revocation status.
+     * @param reasonMask
+     *            The reasons mask which is already checked.
+     * @param certPathCerts
+     *            The certificates of the certification path.
+     * @throws AnnotatedException
+     *             if the certificate is revoked or the status cannot be checked or some error
+     *             occurs.
      */
     private static void checkCRL(
+        PKIXCertRevocationCheckerParameters params,
         DistributionPoint dp,
         PKIXExtendedParameters paramsPKIX,
+        Date currentDate,
+        Date validityDate,
         X509Certificate cert,
-        Date validDate,
         X509Certificate defaultCRLSignCert,
         PublicKey defaultCRLSignKey,
         CertStatus certStatus,
         ReasonsMask reasonMask,
         List certPathCerts,
         JcaJceHelper helper)
-        throws AnnotatedException
+        throws AnnotatedException, RecoverableCertPathValidatorException
     {
-        Date currentDate = new Date(System.currentTimeMillis());
-        if (validDate.getTime() > currentDate.getTime())
+        if (currentDate == null)
+        {
+            boolean debug = true;
+        }
+        if (validityDate.getTime() > currentDate.getTime())
         {
             throw new AnnotatedException("Validation time is in future.");
         }
@@ -1789,7 +1689,7 @@
          * getAdditionalStore()
          */
 
-        Set crls = CertPathValidatorUtilities.getCompleteCRLs(dp, cert, currentDate, paramsPKIX);
+        Set crls = CertPathValidatorUtilities.getCompleteCRLs(params, dp, cert, paramsPKIX, validityDate);
         boolean validCrlFound = false;
         AnnotatedException lastException = null;
         Iterator crl_iter = crls.iterator();
@@ -1822,17 +1722,10 @@
 
                 X509CRL deltaCRL = null;
 
-                Date validityDate = currentDate;
-
-                if (paramsPKIX.getDate() != null)
-                {
-                    validityDate = paramsPKIX.getDate();
-                }
-
                 if (paramsPKIX.isUseDeltasEnabled())
                 {
                     // get delta CRLs
-                    Set deltaCRLs = CertPathValidatorUtilities.getDeltaCRLs(validityDate, crl, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
+                    Set deltaCRLs = CertPathValidatorUtilities.getDeltaCRLs(validityDate, crl, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores(), helper);
                     // we only want one valid delta CRL
                     // (h)
                     deltaCRL = RFC3280CertPathUtilities.processCRLH(deltaCRLs, key);
@@ -1873,10 +1766,10 @@
                 RFC3280CertPathUtilities.processCRLC(deltaCRL, crl, paramsPKIX);
 
                 // (i)
-                RFC3280CertPathUtilities.processCRLI(validDate, deltaCRL, cert, certStatus, paramsPKIX);
+                RFC3280CertPathUtilities.processCRLI(validityDate, deltaCRL, cert, certStatus, paramsPKIX);
 
                 // (j)
-                RFC3280CertPathUtilities.processCRLJ(validDate, crl, cert, certStatus);
+                RFC3280CertPathUtilities.processCRLJ(validityDate, crl, cert, certStatus);
 
                 // (k)
                 if (certStatus.getCertStatus() == CRLReason.removeFromCRL)
@@ -1931,25 +1824,35 @@
     /**
      * Checks a certificate if it is revoked.
      *
-     * @param paramsPKIX       PKIX parameters.
-     * @param cert             Certificate to check if it is revoked.
-     * @param validDate        The date when the certificate revocation status should be
-     *                         checked.
-     * @param sign             The issuer certificate of the certificate <code>cert</code>.
-     * @param workingPublicKey The public key of the issuer certificate <code>sign</code>.
-     * @param certPathCerts    The certificates of the certification path.
-     * @throws AnnotatedException if the certificate is revoked or the status cannot be checked
-     *                            or some error occurs.
+     * @param paramsPKIX
+     *            PKIX parameters.
+     * @param currentDate
+     *            The date at which this check is being run.
+     * @param validityDate
+     *            The date when the certificate revocation status should be checked.
+     * @param cert
+     *            Certificate to check if it is revoked.
+     * @param sign
+     *            The issuer certificate of the certificate <code>cert</code>.
+     * @param workingPublicKey
+     *            The public key of the issuer certificate <code>sign</code>.
+     * @param certPathCerts
+     *            The certificates of the certification path.
+     * @throws AnnotatedException
+     *             if the certificate is revoked or the status cannot be checked or some error
+     *             occurs.
      */
     protected static void checkCRLs(
+        PKIXCertRevocationCheckerParameters params,
         PKIXExtendedParameters paramsPKIX,
+        Date currentDate,
+        Date validityDate,
         X509Certificate cert,
-        Date validDate,
         X509Certificate sign,
         PublicKey workingPublicKey,
         List certPathCerts,
         JcaJceHelper helper)
-        throws AnnotatedException
+        throws AnnotatedException, RecoverableCertPathValidatorException
     {
         AnnotatedException lastException = null;
         CRLDistPoint crldp = null;
@@ -1966,7 +1869,8 @@
         PKIXExtendedParameters.Builder paramsBldr = new PKIXExtendedParameters.Builder(paramsPKIX);
         try
         {
-            List extras = CertPathValidatorUtilities.getAdditionalStoresFromCRLDistributionPoint(crldp, paramsPKIX.getNamedCRLStoreMap());
+            List extras = CertPathValidatorUtilities.getAdditionalStoresFromCRLDistributionPoint(crldp,
+                paramsPKIX.getNamedCRLStoreMap(), validityDate, helper);
             for (Iterator it = extras.iterator(); it.hasNext();)
             {
                 paramsBldr.addCRLStore((PKIXCRLStore)it.next());
@@ -2000,7 +1904,8 @@
                 {
                     try
                     {
-                        checkCRL(dps[i], finalParams, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask, certPathCerts, helper);
+                        checkCRL(params, dps[i], finalParams, currentDate, validityDate, cert, sign, workingPublicKey,
+                            certStatus, reasonsMask, certPathCerts, helper);
                         validCrlFound = true;
                     }
                     catch (AnnotatedException e)
@@ -2029,7 +1934,7 @@
                 X500Name issuer;
                 try
                 {
-                    issuer = PrincipalUtils.getEncodedIssuerPrincipal(cert);
+                    issuer = PrincipalUtils.getIssuerPrincipal(cert);
                 }
                 catch (RuntimeException e)
                 {
@@ -2038,8 +1943,8 @@
                 DistributionPoint dp = new DistributionPoint(new DistributionPointName(0, new GeneralNames(
                     new GeneralName(GeneralName.directoryName, issuer))), null, null);
                 PKIXExtendedParameters paramsPKIXClone = (PKIXExtendedParameters)paramsPKIX.clone();
-                checkCRL(dp, paramsPKIXClone, cert, validDate, sign, workingPublicKey, certStatus, reasonsMask,
-                    certPathCerts, helper);
+                checkCRL(params, dp, paramsPKIXClone, currentDate, validityDate, cert, sign, workingPublicKey,
+                    certStatus, reasonsMask, certPathCerts, helper);
                 validCrlFound = true;
             }
             catch (AnnotatedException e)
@@ -2218,9 +2123,9 @@
         //
         // (n)
         //
-        boolean[] _usage = cert.getKeyUsage();
+        boolean[] keyUsage = cert.getKeyUsage();
 
-        if ((_usage != null) && !_usage[RFC3280CertPathUtilities.KEY_CERT_SIGN])
+        if (keyUsage != null && (keyUsage.length <= KEY_CERT_SIGN || !keyUsage[KEY_CERT_SIGN]))
         {
             throw new ExtCertPathValidatorException(
                 "Issuer certificate keyusage extension is critical and does not permit key signing.", null,
@@ -2373,7 +2278,7 @@
         ASN1Sequence pc = null;
         try
         {
-            pc = DERSequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
+            pc = ASN1Sequence.getInstance(CertPathValidatorUtilities.getExtensionValue(cert,
                 RFC3280CertPathUtilities.POLICY_CONSTRAINTS));
         }
         catch (AnnotatedException e)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
index ea026c7..48f043d 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/RFC3281CertPathUtilities.java
@@ -21,15 +21,14 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 import javax.security.auth.x500.X500Principal;
 
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.CRLDistPoint;
 import org.bouncycastle.asn1.x509.CRLReason;
 import org.bouncycastle.asn1.x509.DistributionPoint;
@@ -40,6 +39,7 @@
 import org.bouncycastle.asn1.x509.TargetInformation;
 import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.jcajce.PKIXCRLStore;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
 import org.bouncycastle.jcajce.PKIXCertStoreSelector;
 import org.bouncycastle.jcajce.PKIXExtendedBuilderParameters;
 import org.bouncycastle.jcajce.PKIXExtendedParameters;
@@ -113,21 +113,24 @@
     /**
      * Checks if an attribute certificate is revoked.
      * 
-     * @param attrCert Attribute certificate to check if it is revoked.
-     * @param paramsPKIX PKIX parameters.
-     * @param issuerCert The issuer certificate of the attribute certificate
-     *            <code>attrCert</code>.
-     * @param validDate The date when the certificate revocation status should
-     *            be checked.
-     * @param certPathCerts The certificates of the certification path to be
-     *            checked.
+     * @param attrCert
+     *            Attribute certificate to check if it is revoked.
+     * @param paramsPKIX
+     *            PKIX parameters.
+     * @param validityDate
+     *            The date when the certificate revocation status should be checked.
+     * @param issuerCert
+     *            The issuer certificate of the attribute certificate <code>attrCert</code>.
+     * @param certPathCerts
+     *            The certificates of the certification path to be checked.
      * 
-     * @throws CertPathValidatorException if the certificate is revoked or the
-     *             status cannot be checked or some error occurs.
+     * @throws CertPathValidatorException
+     *             if the certificate is revoked or the status cannot be checked or some error
+     *             occurs.
      */
-    protected static void checkCRLs(X509AttributeCertificate attrCert,
-        PKIXExtendedParameters paramsPKIX, X509Certificate issuerCert,
-        Date validDate, List certPathCerts, JcaJceHelper helper) throws CertPathValidatorException
+    protected static void checkCRLs(X509AttributeCertificate attrCert, PKIXExtendedParameters paramsPKIX,
+        Date currentDate, Date validityDate, X509Certificate issuerCert, List certPathCerts, JcaJceHelper helper)
+        throws CertPathValidatorException
     {
         if (paramsPKIX.isRevocationEnabled())
         {
@@ -151,7 +154,8 @@
 
                 try
                 {
-                    crlStores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromCRLDistributionPoint(crldp, paramsPKIX.getNamedCRLStoreMap()));
+                    crlStores.addAll(CertPathValidatorUtilities.getAdditionalStoresFromCRLDistributionPoint(crldp,
+                        paramsPKIX.getNamedCRLStoreMap(), validityDate, helper));
                 }
                 catch (AnnotatedException e)
                 {
@@ -196,9 +200,8 @@
                             PKIXExtendedParameters paramsPKIXClone = (PKIXExtendedParameters)paramsPKIX
                                     .clone();
 
-                            checkCRL(dps[i], attrCert, paramsPKIXClone,
-                                validDate, issuerCert, certStatus, reasonsMask,
-                                certPathCerts, helper);
+                            checkCRL(dps[i], attrCert, paramsPKIXClone, currentDate, validityDate, issuerCert,
+                                certStatus, reasonsMask, certPathCerts, helper);
                             validCrlFound = true;
                         }
                     }
@@ -225,14 +228,10 @@
                          * fields omitted and a distribution point name of the
                          * certificate issuer.
                          */
-                        ASN1Primitive issuer = null;
+                        X500Name issuer;
                         try
                         {
-
-                            issuer = new ASN1InputStream(
-                                ((X500Principal) attrCert.getIssuer()
-                                    .getPrincipals()[0]).getEncoded())
-                                .readObject();
+                            issuer = PrincipalUtils.getEncodedIssuerPrincipal(attrCert);
                         }
                         catch (Exception e)
                         {
@@ -246,8 +245,9 @@
                                     issuer))), null, null);
                         PKIXExtendedParameters paramsPKIXClone = (PKIXExtendedParameters) paramsPKIX
                             .clone();
-                        checkCRL(dp, attrCert, paramsPKIXClone, validDate,
-                            issuerCert, certStatus, reasonsMask, certPathCerts, helper);
+ 
+                        checkCRL(dp, attrCert, paramsPKIXClone, currentDate, validityDate, issuerCert, certStatus,
+                            reasonsMask, certPathCerts, helper);
                         validCrlFound = true;
                     }
                     catch (AnnotatedException e)
@@ -323,13 +323,12 @@
         }
     }
 
-    protected static void processAttrCert5(X509AttributeCertificate attrCert,
-        PKIXExtendedParameters pkixParams) throws CertPathValidatorException
+    protected static void processAttrCert5(X509AttributeCertificate attrCert, Date validityDate)
+        throws CertPathValidatorException
     {
         try
         {
-            attrCert.checkValidity(CertPathValidatorUtilities
-                .getValidDate(pkixParams));
+            attrCert.checkValidity(validityDate);
         }
         catch (CertificateExpiredException e)
         {
@@ -368,8 +367,8 @@
     protected static void processAttrCert3(X509Certificate acIssuerCert,
         PKIXExtendedParameters pkixParams) throws CertPathValidatorException
     {
-        if (acIssuerCert.getKeyUsage() != null
-            && (!acIssuerCert.getKeyUsage()[0] && !acIssuerCert.getKeyUsage()[1]))
+        boolean[] keyUsage = acIssuerCert.getKeyUsage();
+        if (keyUsage != null && !((keyUsage.length > 0 && keyUsage[0]) || (keyUsage.length > 1 && keyUsage[1])))
         {
             throw new CertPathValidatorException(
                 "Attribute certificate issuer public key cannot be used to validate digital signatures.");
@@ -440,7 +439,7 @@
     {
         CertPathBuilderResult result = null;
         // find holder PKCs
-        Set holderPKCs = new HashSet();
+        LinkedHashSet holderPKCs = new LinkedHashSet();
         if (attrCert.getHolder().getIssuer() != null)
         {
             X509CertSelector selector = new X509CertSelector();
@@ -455,8 +454,8 @@
                         selector.setIssuer(((X500Principal)principals[i])
                             .getEncoded());
                     }
-                    holderPKCs.addAll(CertPathValidatorUtilities
-                        .findCertificates(new PKIXCertStoreSelector.Builder(selector).build(), pkixParams.getCertStores()));
+                    PKIXCertStoreSelector certSelect = new PKIXCertStoreSelector.Builder(selector).build();
+                    CertPathValidatorUtilities.findCertificates(holderPKCs, certSelect, pkixParams.getCertStores());
                 }
                 catch (AnnotatedException e)
                 {
@@ -489,8 +488,8 @@
                         selector.setIssuer(((X500Principal) principals[i])
                             .getEncoded());
                     }
-                    holderPKCs.addAll(CertPathValidatorUtilities
-                        .findCertificates(new PKIXCertStoreSelector.Builder(selector).build(), pkixParams.getCertStores()));
+                    PKIXCertStoreSelector certSelect = new PKIXCertStoreSelector.Builder(selector).build();
+                    CertPathValidatorUtilities.findCertificates(holderPKCs, certSelect, pkixParams.getCertStores());
                 }
                 catch (AnnotatedException e)
                 {
@@ -558,28 +557,32 @@
     }
 
     /**
+     * Checks a distribution point for revocation information for the certificate
+     * <code>attrCert</code>.
      * 
-     * Checks a distribution point for revocation information for the
-     * certificate <code>attrCert</code>.
-     * 
-     * @param dp The distribution point to consider.
-     * @param attrCert The attribute certificate which should be checked.
-     * @param paramsPKIX PKIX parameters.
-     * @param validDate The date when the certificate revocation status should
-     *            be checked.
-     * @param issuerCert Certificate to check if it is revoked.
-     * @param reasonMask The reasons mask which is already checked.
-     * @param certPathCerts The certificates of the certification path to be
-     *            checked.
-     * @throws AnnotatedException if the certificate is revoked or the status
-     *             cannot be checked or some error occurs.
+     * @param dp
+     *            The distribution point to consider.
+     * @param attrCert
+     *            The attribute certificate which should be checked.
+     * @param paramsPKIX
+     *            PKIX parameters.
+     * @param validDate
+     *            The date when the certificate revocation status should be checked.
+     * @param issuerCert
+     *            Certificate to check if it is revoked.
+     * @param reasonMask
+     *            The reasons mask which is already checked.
+     * @param certPathCerts
+     *            The certificates of the certification path to be checked.
+     * @throws AnnotatedException
+     *             if the certificate is revoked or the status cannot be checked or some error
+     *             occurs.
      */
-    private static void checkCRL(DistributionPoint dp,
-        X509AttributeCertificate attrCert, PKIXExtendedParameters paramsPKIX,
-        Date validDate, X509Certificate issuerCert, CertStatus certStatus,
-        ReasonsMask reasonMask, List certPathCerts, JcaJceHelper helper) throws AnnotatedException
+    private static void checkCRL(DistributionPoint dp, X509AttributeCertificate attrCert,
+        PKIXExtendedParameters paramsPKIX, Date currentDate, Date validityDate, X509Certificate issuerCert, CertStatus certStatus,
+        ReasonsMask reasonMask, List certPathCerts, JcaJceHelper helper)
+        throws AnnotatedException, RecoverableCertPathValidatorException
     {
-
         /*
          * 4.3.6 No Revocation Available
          * 
@@ -591,8 +594,8 @@
         {
             return;
         }
-        Date currentDate = new Date(System.currentTimeMillis());
-        if (validDate.getTime() > currentDate.getTime())
+
+        if (validityDate.getTime() > currentDate.getTime())
         {
             throw new AnnotatedException("Validation time is in future.");
         }
@@ -605,8 +608,9 @@
          * getAdditionalStore()
          */
 
-        Set crls = CertPathValidatorUtilities.getCompleteCRLs(dp, attrCert,
-            currentDate, paramsPKIX);
+        PKIXCertRevocationCheckerParameters params = new PKIXCertRevocationCheckerParameters(paramsPKIX, validityDate,
+            null, -1, issuerCert, null);
+        Set crls = CertPathValidatorUtilities.getCompleteCRLs(params, dp, attrCert, paramsPKIX, validityDate);
         boolean validCrlFound = false;
         AnnotatedException lastException = null;
         Iterator crl_iter = crls.iterator();
@@ -644,7 +648,7 @@
                 if (paramsPKIX.isUseDeltasEnabled())
                 {
                     // get delta CRLs
-                    Set deltaCRLs = CertPathValidatorUtilities.getDeltaCRLs(currentDate, crl, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores());
+                    Set deltaCRLs = CertPathValidatorUtilities.getDeltaCRLs(currentDate, crl, paramsPKIX.getCertStores(), paramsPKIX.getCRLStores(), helper);
                     // we only want one valid delta CRL
                     // (h)
                     deltaCRL = RFC3280CertPathUtilities.processCRLH(deltaCRLs,
@@ -688,12 +692,10 @@
                 RFC3280CertPathUtilities.processCRLC(deltaCRL, crl, paramsPKIX);
 
                 // (i)
-                RFC3280CertPathUtilities.processCRLI(validDate, deltaCRL,
-                    attrCert, certStatus, paramsPKIX);
+                RFC3280CertPathUtilities.processCRLI(validityDate, deltaCRL, attrCert, certStatus, paramsPKIX);
 
                 // (j)
-                RFC3280CertPathUtilities.processCRLJ(validDate, crl, attrCert,
-                    certStatus);
+                RFC3280CertPathUtilities.processCRLJ(validityDate, crl, attrCert, certStatus);
 
                 // (k)
                 if (certStatus.getCertStatus() == CRLReason.removeFromCRL)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/RecoverableCertPathValidatorException.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/RecoverableCertPathValidatorException.java
new file mode 100644
index 0000000..90a2478
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/RecoverableCertPathValidatorException.java
@@ -0,0 +1,13 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidatorException;
+
+class RecoverableCertPathValidatorException
+    extends CertPathValidatorException
+{
+    public RecoverableCertPathValidatorException(String msg, Throwable cause, CertPath certPath, int index)
+    {
+        super(msg, cause, certPath, index);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/WrappedRevocationChecker.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/WrappedRevocationChecker.java
new file mode 100644
index 0000000..c27768b
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/WrappedRevocationChecker.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.jce.provider;
+
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.PKIXCertPathChecker;
+
+import org.bouncycastle.jcajce.PKIXCertRevocationChecker;
+import org.bouncycastle.jcajce.PKIXCertRevocationCheckerParameters;
+
+class WrappedRevocationChecker
+    implements PKIXCertRevocationChecker
+{
+    private final PKIXCertPathChecker checker;
+
+    public WrappedRevocationChecker(PKIXCertPathChecker checker)
+    {
+        this.checker = checker;
+    }
+
+    public void setParameter(String name, Object value)
+    {
+         // ignore.
+    }
+
+    public void initialize(PKIXCertRevocationCheckerParameters params)
+        throws CertPathValidatorException
+    {
+        checker.init(false);
+    }
+
+    public void check(Certificate cert)
+        throws CertPathValidatorException
+    {
+        checker.check(cert);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AEADTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AEADTest.java
index a8f7c26..e6e331a 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AEADTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AEADTest.java
@@ -455,7 +455,7 @@
             eax.init(Cipher.ENCRYPT_MODE, new RepeatedSecretKeySpec("AES"), spec);
             fail("no exception");
         }
-        catch (InvalidKeyException e)
+        catch (InvalidAlgorithmParameterException e)
         {
             isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage()));
         }
@@ -465,7 +465,7 @@
             eax.init(Cipher.ENCRYPT_MODE, new RepeatedSecretKeySpec("AES"), new IvParameterSpec(spec.getIV()));
             fail("no exception");
         }
-        catch (InvalidKeyException e)
+        catch (InvalidAlgorithmParameterException e)
         {
             isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage()));
         }
@@ -475,7 +475,7 @@
             eax.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(K, "AES"), new IvParameterSpec(spec.getIV()));
             fail("no exception");
         }
-        catch (InvalidKeyException e)
+        catch (InvalidAlgorithmParameterException e)
         {
             isTrue("wrong message", "cannot reuse nonce for GCM encryption".equals(e.getMessage()));
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AESTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AESTest.java
index 454232a..6cfca50 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AESTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AESTest.java
@@ -4,7 +4,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
 import java.io.IOException;
-import java.security.InvalidKeyException;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.Key;
 import java.security.Security;
 
@@ -311,7 +311,7 @@
             in.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(N));
             fail("no exception on reuse");
         }
-        catch (InvalidKeyException e)
+        catch (InvalidAlgorithmParameterException e)
         {
             isTrue("wrong message", e.getMessage().equals("cannot reuse nonce for GCM encryption"));
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AllTests.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AllTests.java
index 15e9fbd..63611ca 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AllTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/AllTests.java
@@ -7,48 +7,24 @@
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.util.test.SimpleTestResult;
 
 public class AllTests
     extends TestCase
 {
-    public static void main (String[] args)
+    public static void main(String[] args)
     {
         junit.textui.TestRunner.run(suite());
     }
-    
+
     public static Test suite()
     {
         TestSuite suite = new TestSuite("JCE Tests");
 
         suite.addTestSuite(SimpleTestTest.class);
-        
+
         return new BCTestSetup(suite);
     }
 
-    public static class SimpleTestTest
-       extends TestCase
-    {
-        public void testJCE()
-        {
-            org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
-
-            for (int i = 0; i != tests.length; i++)
-            {
-                SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
-
-                if (!result.isSuccessful())
-                {
-                    if (result.getException() != null)
-                    {
-                        result.getException().printStackTrace();
-                    }
-                    fail(result.toString());
-                }
-            }
-        }
-    }
-
     static class BCTestSetup
         extends TestSetup
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BCFKSStoreTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BCFKSStoreTest.java
index 2df1e7f..9e56123 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BCFKSStoreTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BCFKSStoreTest.java
@@ -539,6 +539,32 @@
         checkOnePrivateKeyDef(kp1.getPrivate(), new X509Certificate[]{interCert, finalCert}, testPassword);
     }
 
+    public void shouldStoreOnePrivateKeyWithChainEdDSA()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EDDSA", "BC");
+
+        kpGen.initialize(448);
+
+        KeyPair kp1 = kpGen.generateKeyPair();
+        KeyPair kp2 = kpGen.generateKeyPair();
+
+        X509Certificate finalCert = TestUtils.createSelfSignedCert("CN=Final", "Ed448", kp2);
+        X509Certificate interCert = TestUtils.createCert(
+            TestUtils.getCertSubject(finalCert),
+            kp2.getPrivate(),
+            "CN=EE",
+            "Ed448",
+            null,
+            kp1.getPublic());
+
+        checkOnePrivateKeyFips(kp1.getPrivate(), new X509Certificate[]{interCert, finalCert}, null);
+        checkOnePrivateKeyFips(kp1.getPrivate(), new X509Certificate[]{interCert, finalCert}, testPassword);
+
+        checkOnePrivateKeyDef(kp1.getPrivate(), new X509Certificate[]{interCert, finalCert}, null);
+        checkOnePrivateKeyDef(kp1.getPrivate(), new X509Certificate[]{interCert, finalCert}, testPassword);
+    }
+
     public void shouldStoreOneECKeyWithChain()
         throws Exception
     {
@@ -1548,6 +1574,7 @@
         shouldParseOldStores();
         shouldStoreUsingKWP();
         //shouldRejectInconsistentKeys();
+        shouldStoreOnePrivateKeyWithChainEdDSA();
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BlockCipherTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BlockCipherTest.java
index c2475c3..bb75c97 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BlockCipherTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/BlockCipherTest.java
@@ -11,6 +11,8 @@
 import java.security.InvalidKeyException;
 import java.security.InvalidParameterException;
 import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
@@ -25,6 +27,7 @@
 import javax.crypto.CipherOutputStream;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.ShortBufferException;
@@ -601,6 +604,23 @@
             "3d9255",
     };
 
+    static String[] cipherModes = new String[]
+    {
+        "OFB",
+        "CFB",
+        "PGP",
+        "OpenPGPCFB",
+        "SIC",
+        "CTR",
+        "GOFB",
+        "GCFB",
+        "CTS",
+        "CCM",
+        "OCB",
+        "EAX",
+        "GCM"
+    };
+
     static byte[]   input1 = Hex.decode("000102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c0d0e0f");
     static byte[]   input2 = Hex.decode("000102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c");
     static byte[]   inputLargeBlock = Hex.decode("000102030405060708090a0b0c0d0e0fff0102030405060708090a0b0c0d0e0f" +
@@ -1578,7 +1598,106 @@
             fail("unexpected exception.", e);
         }
     }
-    
+
+    private void testIncorrectCipherModes()
+    {
+        for (int i = 0; i != cipherModes.length; i++)
+        {
+            try
+            {
+                Cipher.getInstance("AES/" + cipherModes[i] + "NOT_REAL" + "/NoPadding", "BC");
+                fail("\"AES/" + cipherModes[i] + "NOT_REAL/NoPadding\"" + "returned");
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                if (!(e.getMessage().indexOf("can't support mode ") >= 0))      // old JVM
+                {
+                    isEquals("1 got: " + e.getMessage(), "No such algorithm: AES/" + cipherModes[i] + "NOT_REAL/NoPadding", e.getMessage());
+                }
+            }
+            catch (NoSuchPaddingException e)
+            {
+                fail(e.toString());
+            }
+            catch (NoSuchProviderException e)
+            {
+                fail(e.toString());
+            }
+        }
+
+        for (int i = 0; i != cipherModes.length; i++)
+        {
+            try
+            {
+                Cipher.getInstance("AES/" + cipherModes[i] + "256" + "/NoPadding", "BC");
+                fail("\"AES/" + cipherModes[i] + "256/NoPadding\"" + "returned");
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                if (!(e.getMessage().indexOf("can't support mode ") >= 0))      // old JVM
+                {
+                    isEquals("2 got: " + e.getMessage(), "No such algorithm: AES/" + cipherModes[i] + "256/NoPadding", e.getMessage());
+                }
+            }
+            catch (NoSuchPaddingException e)
+            {
+                fail(e.toString());
+            }
+            catch (NoSuchProviderException e)
+            {
+                fail(e.toString());
+            }
+        }
+
+        for (int i = 0; i != cipherModes.length; i++)
+        {
+            try
+            {
+                Cipher.getInstance("AES/" + cipherModes[i] + "2" + "/NoPadding", "BC");
+                fail("\"AES/" + cipherModes[i] + "2/NoPadding\"" + "returned");
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                if (!(e.getMessage().indexOf("can't support mode ") >= 0))      // old JVM
+                {
+                    isEquals("3 got: " + e.getMessage(), "No such algorithm: AES/" + cipherModes[i] + "2/NoPadding", e.getMessage());
+                }
+            }
+            catch (NoSuchPaddingException e)
+            {
+                fail(e.toString());
+            }
+            catch (NoSuchProviderException e)
+            {
+                fail(e.toString());
+            }
+        }
+
+        for (int i = 0; i != cipherModes.length; i++)
+        {
+            try
+            {
+                Cipher.getInstance("AES/" + cipherModes[i] + "9" + "/NoPadding", "BC");
+                fail("\"AES/" + cipherModes[i] + "9/NoPadding\"" + "returned");
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                if (!(e.getMessage().indexOf("can't support mode ") >= 0))      // old JVM
+                {
+                    isEquals("No such algorithm: AES/" + cipherModes[i] + "9/NoPadding", e.getMessage());
+                }
+            }
+            catch (NoSuchPaddingException e)
+            {
+                fail(e.toString());
+            }
+            catch (NoSuchProviderException e)
+            {
+                fail(e.toString());
+            }
+        }
+    }
+
     public void performTest()
     {
         for (int i = 0; i != cipherTests1.length; i += 2)
@@ -1618,6 +1737,7 @@
         }
         
         testExceptions();
+        testIncorrectCipherModes();
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CMacTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CMacTest.java
index e9702e6..5bd9346 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CMacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CMacTest.java
@@ -12,7 +12,7 @@
 import org.bouncycastle.util.test.SimpleTest;
 
 /**
- * CMAC tester - <a href="http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/tv/omac1-tv.txt">AES Official Test Vectors</a>.
+ * CMAC tester - <a href="https://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/tv/omac1-tv.txt">AES Official Test Vectors</a>.
  */
 public class CMacTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathBuilderTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathBuilderTest.java
index 42f5878..8221a86 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathBuilderTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathBuilderTest.java
@@ -117,11 +117,99 @@
         }
     }
 
+    private void eeInSelectorTest()
+        throws Exception
+    {
+        // create certificates and CRLs
+        KeyPair         rootPair = TestUtils.generateRSAKeyPair();
+        KeyPair         interPair = TestUtils.generateRSAKeyPair();
+        KeyPair         endPair = TestUtils.generateRSAKeyPair();
+
+        X509Certificate rootCert = TestUtils.generateRootCert(rootPair);
+        X509Certificate interCert = TestUtils.generateIntermediateCert(interPair.getPublic(), rootPair.getPrivate(), rootCert);
+        X509Certificate endCert = TestUtils.generateEndEntityCert(endPair.getPublic(), interPair.getPrivate(), interCert);
+
+        // create CertStore to support path building
+        List list = new ArrayList();
+
+        list.add(interCert);
+        list.add(endCert);
+        
+        CollectionCertStoreParameters params = new CollectionCertStoreParameters(list);
+        CertStore                     store = CertStore.getInstance("Collection", params, "BC");
+
+        // build the path
+        CertPathBuilder  builder = CertPathBuilder.getInstance("PKIX", "BC");
+        X509CertSelector pathConstraints = new X509CertSelector();
+
+        pathConstraints.setCertificate(endCert);
+
+        PKIXBuilderParameters buildParams = new PKIXBuilderParameters(Collections.singleton(new TrustAnchor(rootCert, null)), pathConstraints);
+
+        buildParams.addCertStore(store);
+        buildParams.setDate(new Date());
+        buildParams.setRevocationEnabled(false);
+
+        PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult)builder.build(buildParams);
+        CertPath                  path = result.getCertPath();
+
+        if (path.getCertificates().size() != 2)
+        {
+            fail("wrong number of certs in v0Test path");
+        }
+    }
+
+    private void eeOnlyInSelectorTest()
+        throws Exception
+    {
+        // create certificates and CRLs
+        KeyPair         rootPair = TestUtils.generateRSAKeyPair();
+        KeyPair         interPair = TestUtils.generateRSAKeyPair();
+        KeyPair         endPair = TestUtils.generateRSAKeyPair();
+        KeyPair         miscPair = TestUtils.generateRSAKeyPair();
+
+        X509Certificate rootCert = TestUtils.generateRootCert(rootPair);
+        X509Certificate interCert = TestUtils.generateIntermediateCert(interPair.getPublic(), rootPair.getPrivate(), rootCert);
+        X509Certificate endCert = TestUtils.generateEndEntityCert(endPair.getPublic(), interPair.getPrivate(), interCert);
+        X509Certificate miscCert = TestUtils.generateEndEntityCert(miscPair.getPublic(), interPair.getPrivate(), interCert);
+
+        // create CertStore to support path building
+        List list = new ArrayList();
+
+        list.add(interCert);
+        list.add(miscCert);
+
+        CollectionCertStoreParameters params = new CollectionCertStoreParameters(list);
+        CertStore                     store = CertStore.getInstance("Collection", params, "BC");
+
+        // build the path
+        CertPathBuilder  builder = CertPathBuilder.getInstance("PKIX", "BC");
+        X509CertSelector pathConstraints = new X509CertSelector();
+
+        pathConstraints.setCertificate(endCert);
+
+        PKIXBuilderParameters buildParams = new PKIXBuilderParameters(Collections.singleton(new TrustAnchor(rootCert, null)), pathConstraints);
+
+        buildParams.addCertStore(store);
+        buildParams.setDate(new Date());
+        buildParams.setRevocationEnabled(false);
+
+        PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult)builder.build(buildParams);
+        CertPath                  path = result.getCertPath();
+
+        if (path.getCertificates().size() != 2)
+        {
+            fail("wrong number of certs in v0Test path");
+        }
+    }
+
     public void performTest()
         throws Exception
     {
         baseTest();
         v0Test();
+        eeInSelectorTest();
+        eeOnlyInSelectorTest();
     }
     
     public String getName()
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
index be8416b..85d12d7 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.jce.provider.test;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
 import java.net.InetAddress;
@@ -53,7 +52,6 @@
 import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
-import org.bouncycastle.asn1.ASN1OutputStream;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.ASN1String;
@@ -362,9 +360,38 @@
         }
     }
 
+
+    private void constraintTest()
+        throws Exception
+    {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
+
+        X509Certificate rootCert = (X509Certificate)cf.generateCertificate(this.getClass().getResourceAsStream("CERT_CI_ECDSA_NIST.pem"));
+        X509Certificate interCert = (X509Certificate)cf.generateCertificate(this.getClass().getResourceAsStream("CERT_EUM_ECDSA_NIST.pem"));
+        X509Certificate finalCert = (X509Certificate)cf.generateCertificate(this.getClass().getResourceAsStream("CERT_EUICC_ECDSA_NIST.pem"));
+
+        List list = new ArrayList();
+        list.add(interCert);
+        list.add(finalCert);
+
+        CertPath certPath = cf.generateCertPath(list);
+
+        Set trust = new HashSet();
+        trust.add(new TrustAnchor(rootCert, null));
+
+        CertPathValidator cpv = CertPathValidator.getInstance("PKIX", "BC");
+        PKIXParameters param = new PKIXParameters(trust);
+        param.setRevocationEnabled(false);
+
+        cpv.validate(certPath, param);
+
+    }
+
     public void performTest()
         throws Exception
     {
+        constraintTest();
+
         CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC");
 
         // initialise CertStore
@@ -770,12 +797,7 @@
         {
             try
             {
-                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-                ASN1OutputStream aOut = new ASN1OutputStream(bOut);
-
-                aOut.writeObject(c.getIssuer());
-
-                return new X500Principal(bOut.toByteArray());
+                return new X500Principal(c.getIssuer().getEncoded());
             }
             catch (IOException e)
             {
@@ -792,12 +814,7 @@
         {
             try
             {
-                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
-                ASN1OutputStream aOut = new ASN1OutputStream(bOut);
-
-                aOut.writeObject(c.getSubject());
-
-                return new X500Principal(bOut.toByteArray());
+                return new X500Principal(c.getSubject().getEncoded());
             }
             catch (IOException e)
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertTest.java
index 9f7b0a6..ac5b611 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CertTest.java
@@ -2,7 +2,10 @@
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 import java.security.AlgorithmParameters;
@@ -12,7 +15,9 @@
 import java.security.NoSuchProviderException;
 import java.security.PublicKey;
 import java.security.Security;
+import java.security.SignatureException;
 import java.security.cert.CRL;
+import java.security.cert.CRLException;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
@@ -21,6 +26,7 @@
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import java.security.spec.X509EncodedKeySpec;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -1153,6 +1159,34 @@
 
     }
 
+    public void checkCertificate(
+        int id,
+        byte[] bytes,
+        PublicKey pubKey)
+    {
+        ByteArrayInputStream bIn;
+        String dump = "";
+
+        try
+        {
+            bIn = new ByteArrayInputStream(bytes);
+
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(bIn);
+
+            PublicKey k = cert.getPublicKey();
+
+            cert.verify(pubKey);
+            //            System.out.println(cert);
+        }
+        catch (Exception e)
+        {
+            fail(dump + Strings.lineSeparator() + getName() + ": " + id + " failed - exception " + e.toString(), e);
+        }
+
+    }
+
     public void checkNameCertificate(
         int id,
         byte[] bytes)
@@ -1199,7 +1233,8 @@
 
             PublicKey k = cert.getPublicKey();
 
-            if (cert.getKeyUsage()[7])
+            boolean[] keyUsage = cert.getKeyUsage();
+            if (keyUsage == null || keyUsage.length <= 7 || keyUsage[7])
             {
                 fail("error generating cert - key usage wrong.");
             }
@@ -1541,6 +1576,27 @@
         x509.verify(x509.getPublicKey(), "BC");
     }
 
+    private void testCertificateSerialization()
+        throws Exception
+    {
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        X509Certificate x509 = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(gostRFC4491_2001));
+
+        oOut.writeObject(x509);
+        
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        x509 = (X509Certificate)oIn.readObject();
+
+        x509.verify(x509.getPublicKey(), "BC");
+    }
+
     private void checkComparison(byte[] encCert)
         throws NoSuchProviderException, CertificateException
     {
@@ -1666,10 +1722,24 @@
     {
         CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
 
-        Collection crls = certFact.generateCRLs(this.getClass().getResourceAsStream("cert_chain.txt"));
-        isTrue("multi crl", crls.isEmpty());
-        CRL crl = certFact.generateCRL(this.getClass().getResourceAsStream("cert_chain.txt"));
-        isTrue("single crl", crl == null);
+        try
+        {
+            certFact.generateCRLs(this.getClass().getResourceAsStream("cert_chain.txt"));
+            fail("multi crl - no exception");
+        }
+        catch (CRLException e)
+        {
+            // ignore
+        }
+        try
+        {
+            certFact.generateCRL(this.getClass().getResourceAsStream("cert_chain.txt"));
+            fail("single crl - no exception");
+        }
+        catch (CRLException e)
+        {
+            // ignore
+        }
     }
 
     private void pemFileTestWithNl()
@@ -1719,8 +1789,29 @@
         checkCertificate(6, oldEcdsa);
         checkCertificate(7, cert7);
         checkCertificate(8, sm_sign);
-        checkCertificate(9, x25519Cert);
 
+        System.setProperty("org.bouncycastle.x509.allow_non-der_tbscert", "true");
+
+        checkCertificate(9, x25519Cert,
+            KeyFactory.getInstance("EdDSA").generatePublic(new X509EncodedKeySpec(Base64.decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE="))));
+
+        System.setProperty("org.bouncycastle.x509.allow_non-der_tbscert", "false");
+
+        try
+        {
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+
+            Certificate cert = fact.generateCertificate(new ByteArrayInputStream(x25519Cert));
+
+            cert.verify(KeyFactory.getInstance("EdDSA").generatePublic(new X509EncodedKeySpec(Base64.decode("MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE="))));
+
+            fail("no exception");
+        }
+        catch (SignatureException e)
+        {
+            isEquals("certificate does not verify with supplied key", e.getMessage());
+        }
+        
         checkComparison(cert1);
 
         checkKeyUsage(8, keyUsage);
@@ -1769,6 +1860,7 @@
         invalidCRLs();
 
         testForgedSignature();
+        testCertificateSerialization();
 
         checkCertificate(18, emptyDNCert);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ChaCha20Poly1305Test.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ChaCha20Poly1305Test.java
new file mode 100644
index 0000000..b3cf3b5
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ChaCha20Poly1305Test.java
@@ -0,0 +1,303 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.jcajce.spec.AEADParameterSpec;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class ChaCha20Poly1305Test
+    extends SimpleTest
+{
+    private static final String[][] TEST_VECTORS = new String[][] {
+    {
+        "Test Case 1",
+        "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+        "4c616469657320616e642047656e746c"
+        + "656d656e206f662074686520636c6173"
+        + "73206f66202739393a20496620492063"
+        + "6f756c64206f6666657220796f75206f"
+        + "6e6c79206f6e652074697020666f7220"
+        + "746865206675747572652c2073756e73"
+        + "637265656e20776f756c642062652069"
+        + "742e",
+        "50515253c0c1c2c3c4c5c6c7",
+        "070000004041424344454647",
+        "d31a8d34648e60db7b86afbc53ef7ec2"
+        + "a4aded51296e08fea9e2b5a736ee62d6"
+        + "3dbea45e8ca9671282fafb69da92728b"
+        + "1a71de0a9e060b2905d6a5b67ecd3b36"
+        + "92ddbd7f2d778b8c9803aee328091b58"
+        + "fab324e4fad675945585808b4831d7bc"
+        + "3ff4def08e4b7a9de576d26586cec64b"
+        + "6116",
+        "1ae10b594f09e26a7e902ecbd0600691",
+    },
+    };
+
+    private boolean aeadAvailable = false;
+
+    public String getName()
+    {
+        return "ChaCha20Poly1305";
+    }
+
+    public void performTest() throws Exception
+    {
+        try
+        {
+            this.getClass().getClassLoader().loadClass("javax.crypto.spec.GCMParameterSpec");
+            aeadAvailable = true;
+        }
+        catch (ClassNotFoundException e)
+        {
+        }
+
+        for (int i = 0; i < TEST_VECTORS.length; ++i)
+        {
+            runTestCase(TEST_VECTORS[i]);
+        }
+
+        // basic test using oids
+        byte[] msg = Strings.toByteArray("Hello, world!");
+
+        KeyGenerator keyGen = KeyGenerator.getInstance(PKCSObjectIdentifiers.id_alg_AEADChaCha20Poly1305.getId(), "BC");
+        Cipher encCipher = Cipher.getInstance(PKCSObjectIdentifiers.id_alg_AEADChaCha20Poly1305.getId(), "BC");
+        SecretKey key = keyGen.generateKey();
+        encCipher.init(Cipher.ENCRYPT_MODE, key);
+
+        byte[] enc = encCipher.doFinal(msg);
+
+        Cipher decCipher = Cipher.getInstance(PKCSObjectIdentifiers.id_alg_AEADChaCha20Poly1305.getId(), "BC");
+
+        decCipher.init(Cipher.DECRYPT_MODE, key, encCipher.getParameters());
+
+        byte[] dec = decCipher.doFinal(enc);
+
+        areEqual(msg, dec);
+
+        // check mac failure
+        byte[] faulty = new byte[enc.length];
+        System.arraycopy(enc, 0, faulty, 0, enc.length - 1);
+
+        try
+        {
+            decCipher.doFinal(faulty);
+            fail("no exception");
+        }
+        catch (Exception e)
+        {
+            if (aeadAvailable)
+            {
+                if (!e.getClass().getName().equals("javax.crypto.AEADBadTagException"))
+                {
+                    fail("Tampered AEAD ciphertext should fail with AEADBadTagException when available.");
+                }
+            }
+            isEquals("mac check in ChaCha20Poly1305 failed", e.getMessage());
+        }
+
+        System.arraycopy(enc, 0, faulty, 0, enc.length);
+        faulty[0] ^= -1;
+        decCipher.init(Cipher.DECRYPT_MODE, key, encCipher.getParameters());
+        try
+        {
+            decCipher.doFinal(faulty);
+            fail("no exception");
+        }
+        catch (Exception e)
+        {
+            if (aeadAvailable)
+            {
+                if (!e.getClass().getName().equals("javax.crypto.AEADBadTagException"))
+                {
+                    fail("Tampered AEAD ciphertext should fail with AEADBadTagException when available.");
+                }
+            }
+            isEquals("mac check in ChaCha20Poly1305 failed", e.getMessage());
+        }
+        //
+        // check for alg params.
+        AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("ChaCha20-Poly1305", "BC"); // to be sure
+        algorithmParameters = AlgorithmParameters.getInstance(PKCSObjectIdentifiers.id_alg_AEADChaCha20Poly1305.getId(), "BC");
+
+        GitHub674Test();
+    }
+
+    private void checkTestCase(
+        Cipher  encCipher,
+        Cipher  decCipher,
+        String  testName,
+        byte[]  SA,
+        byte[]  P,
+        byte[]  C,
+        byte[]  T)
+        throws Exception
+    {
+        byte[] enc = new byte[encCipher.getOutputSize(P.length)];
+        if (SA != null)
+        {
+            encCipher.updateAAD(SA, 0, SA.length);
+        }
+        int len = encCipher.update(P, 0, P.length, enc, 0);
+        len += encCipher.doFinal(enc, len);
+
+        if (enc.length != len)
+        {
+            fail("encryption reported incorrect length: " + testName);
+        }
+
+        //byte[] mac = encCipher.getMac();
+
+        byte[] data = new byte[P.length];
+        System.arraycopy(enc, 0, data, 0, data.length);
+        byte[] tail = new byte[enc.length - P.length];
+        System.arraycopy(enc, P.length, tail, 0, tail.length);
+
+        if (!areEqual(C, data))
+        {
+            fail("incorrect encrypt in: " + testName);
+        }
+
+//        if (!areEqual(T, mac))
+//        {
+//            fail("getMac() returned wrong mac in: " + testName);
+//        }
+
+        if (!areEqual(T, tail))
+        {
+            fail("stream contained wrong mac in: " + testName);
+        }
+
+        byte[] dec = new byte[decCipher.getOutputSize(enc.length)];
+        if (SA != null)
+        {
+            decCipher.updateAAD(SA, 0, SA.length);
+        }
+        len = decCipher.update(enc, 0, enc.length, dec, 0);
+        len += decCipher.doFinal(dec, len);
+       // mac = decCipher.getMac();
+
+        data = new byte[C.length];
+        System.arraycopy(dec, 0, data, 0, data.length);
+
+        if (!areEqual(P, data))
+        {
+            fail("incorrect decrypt in: " + testName);
+        }
+    }
+
+    private Cipher initCipher(boolean forEncryption, Key key, IvParameterSpec iv)
+        throws GeneralSecurityException
+    {
+        Cipher c = Cipher.getInstance("ChaCha20-Poly1305", "BC");
+
+        c.init(forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, iv);
+        return c;
+    }
+
+    private void runTestCase(String[] testVector)
+        throws Exception
+    {
+        int pos = 0;
+        String testName = testVector[pos++];
+        byte[] K = Hex.decode(testVector[pos++]);
+        byte[] P = Hex.decode(testVector[pos++]);
+        byte[] A = Hex.decode(testVector[pos++]);
+        byte[] N = Hex.decode(testVector[pos++]);
+        byte[] C = Hex.decode(testVector[pos++]);
+        byte[] T = Hex.decode(testVector[pos++]);
+
+        runTestCase(testName, K, N, A, P, C, T);
+    }
+
+    private void runTestCase(
+        String  testName,
+        byte[]  K,
+        byte[]  N,
+        byte[]  A,
+        byte[]  P,
+        byte[]  C,
+        byte[]  T)
+        throws Exception
+    {
+        byte[] fa = new byte[A.length / 2];
+        byte[] la = new byte[A.length - (A.length / 2)];
+        System.arraycopy(A, 0, fa, 0, fa.length);
+        System.arraycopy(A, fa.length, la, 0, la.length);
+
+        runTestCase(testName + " all initial associated data", K, N, A, null, P, C, T);
+
+        if (aeadAvailable)
+        {
+            runTestCase(testName + " all subsequent associated data", K, N, null, A, P, C, T);
+            runTestCase(testName + " split associated data", K, N, fa, la, P, C, T);
+        }
+    }
+
+    private void runTestCase(
+        String  testName,
+        byte[]  K,
+        byte[]  N,
+        byte[]  A,
+        byte[]  SA,
+        byte[]  P,
+        byte[]  C,
+        byte[]  T)
+        throws Exception
+    {
+        SecretKeySpec keySpec = new SecretKeySpec(K, "ChaCha20");
+        AEADParameterSpec parameters = new AEADParameterSpec(N, T.length * 8, A);
+        Cipher encCipher = initCipher(true, keySpec, parameters);
+        Cipher decCipher = initCipher(false, keySpec, parameters);
+        checkTestCase(encCipher, decCipher, testName, SA, P, C, T);
+        encCipher = initCipher(true, keySpec, parameters);
+
+        AlgorithmParameters algParams = decCipher.getParameters();
+
+        IvParameterSpec ivSpec = (IvParameterSpec)algParams.getParameterSpec(AlgorithmParameterSpec.class);
+
+        isTrue(areEqual(ivSpec.getIV(), N));
+        isTrue(areEqual(encCipher.getIV(), N));
+        
+        checkTestCase(encCipher, decCipher, testName + " (reused)", SA, P, C, T);
+    }
+
+    private void GitHub674Test() throws Exception
+    {
+        byte[] K = new byte[32];
+        byte[] N = new byte[12];
+        byte[] A = new byte[0];
+
+        SecretKeySpec keySpec = new SecretKeySpec(K, "ChaCha20");
+        AEADParameterSpec parameters = new AEADParameterSpec(N, 128, A);
+        Cipher encCipher = initCipher(true, keySpec, parameters);
+
+        /*
+         * This resulted in a NullPointerException before fix. It depended on the update length (63
+         * being less than the internal buffer size of the ChaCha20Poly1305 engine.
+         */
+        byte[] encrypted = encCipher.update(new byte[63]);
+        isTrue(null == encrypted);
+    }
+
+    public static void main(String[] args) throws Exception
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new ChaCha20Poly1305Test());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java
index 8f4cf01..cc0d255 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/CipherStreamTest2.java
@@ -60,14 +60,29 @@
                 testWriteReadEmpty(cipherName, key, authenticated, false, true);
             }
 
+
+
             if (authenticated)
             {
+                boolean aeadAvailable = false;
+                try
+                {
+                    this.getClass().getClassLoader().loadClass("javax.crypto.spec.GCMParameterSpec");
+                    aeadAvailable = true;
+                }
+                catch (ClassNotFoundException e)
+                {
+                }
+
                 testTamperedRead(cipherName, key, true, true);
-                testTamperedRead(cipherName, key, true, false);
                 testTruncatedRead(cipherName, key, true, true);
-                testTruncatedRead(cipherName, key, true, false);
                 testTamperedWrite(cipherName, key, true, true);
-                testTamperedWrite(cipherName, key, true, false);
+                if (aeadAvailable)
+                {
+                    testTamperedRead(cipherName, key, true, false);
+                    testTruncatedRead(cipherName, key, true, false);
+                    testTamperedWrite(cipherName, key, true, false);
+                }
             }
         }
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DSTU4145Test.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DSTU4145Test.java
index a07b919..5ad2992 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DSTU4145Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DSTU4145Test.java
@@ -58,7 +58,7 @@
 
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16), false),
+            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16)),
             new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16));
         
         SecureRandom k = new TestRandomBigInteger(Hex.decode("00137449348C1249971759D99C252FFE1E14D8B31F00"));
@@ -137,7 +137,7 @@
 
         ECParameterSpec spec = new ECParameterSpec(
             curve,
-            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16), false),
+            curve.createPoint(new BigInteger("BE6628EC3E67A91A4E470894FBA72B52C515F8AEE9", 16), new BigInteger("D9DEEDF655CF5412313C11CA566CDC71F4DA57DB45C", 16)),
             new BigInteger("800000000000000000000189B4E67606E3825BB2831", 16));
 
         ECPrivateKeySpec priKey = new ECPrivateKeySpec(
@@ -145,7 +145,7 @@
             spec);
 
         ECPublicKeySpec pubKey = new ECPublicKeySpec(
-            curve.createPoint(new BigInteger("22de541d48a75c1c3b8c7c107b2551c5093c6c096e1", 16), new BigInteger("1e5b602efc0269d61e64d97c9193d2788fa05c4b7fd5", 16), false),
+            curve.createPoint(new BigInteger("22de541d48a75c1c3b8c7c107b2551c5093c6c096e1", 16), new BigInteger("1e5b602efc0269d61e64d97c9193d2788fa05c4b7fd5", 16)),
             spec);
 
         Signature sgr = Signature.getInstance("DSTU4145", "BC");
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DigestTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DigestTest.java
index e9b4a58..0bc193c 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DigestTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/DigestTest.java
@@ -10,6 +10,7 @@
 import org.bouncycastle.asn1.teletrust.TeleTrusTObjectIdentifiers;
 import org.bouncycastle.asn1.ua.UAObjectIdentifiers;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -50,6 +51,8 @@
         { NISTObjectIdentifiers.id_sha3_256.getId(), "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532" },
         { NISTObjectIdentifiers.id_sha3_384.getId(), "ec01498288516fc926459f58e2c6ad8df9b473cb0fc08c2596da7cf0e49be4b298d88cea927ac7f539f1edf228376d25" },
         { NISTObjectIdentifiers.id_sha3_512.getId(), "b751850b1a57168a5693cd924b6b096e08f621827444f70d884f5d0240d2712e10e116e9192af3c91a7ec57647e3934057340b4cf408d5a56592f8274eec53f0" },
+        {"SHAKE128", "5881092dd818bf5cf8a3ddb793fbcba74097d5c526a6d35f97b83351940f2cc8"},
+                    {"SHAKE256", "483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739d5a15bef186a5386c75744c0527e1faa9f8726e462a12a4feb06bd8801e751e4"},
         { "KECCAK-224", "c30411768506ebe1c2871b1ee2e87d38df342317300a9b97a95ec6a8" },
         { "KECCAK-256", "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45" },
         { "KECCAK-288", "20ff13d217d5789fa7fc9e0e9a2ee627363ec28171d0b6c52bbd2f240554dbc94289f4d6" },
@@ -88,11 +91,9 @@
         return "Digest";
     }
 
-    void test(String algorithm)
+    void test(String algorithm, byte[] message)
         throws Exception
     {
-        byte[] message = "hello world".getBytes();
-
         MessageDigest digest = MessageDigest.getInstance(algorithm, provider);
 
         byte[] result = digest.digest(message);
@@ -157,6 +158,11 @@
             fail("Result object 5 not equal");
         }
 
+        // Haraka has a fixed length input
+        if (algorithm.startsWith("HARAKA"))
+        {
+            return;
+        }
         // test six, check reset() method with longer message
         digest.update(message);
         digest.update(message);
@@ -189,7 +195,7 @@
         byte[] result = digest.digest(abc);
         
         if (!MessageDigest.isEqual(result, Hex.decode(hash)))
-        {                  System.err.println(Hex.toHexString(result));
+        {
             fail("abc result not equal for " + algorithm);
         }
     }
@@ -199,10 +205,14 @@
     {
         for (int i = 0; i != abcVectors.length; i++)
         {
-            test(abcVectors[i][0]);
+            test(abcVectors[i][0], Strings.toByteArray("hello world"));
          
             abcTest(abcVectors[i][0], abcVectors[i][1]);
         }
+
+        test("HARAKA-256", Hex.decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"));
+        test("HARAKA-512", Hex.decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+                                    + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
     }
 
     public static void main(String[] args)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECDSA5Test.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECDSA5Test.java
index b37e301..6445021 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECDSA5Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECDSA5Test.java
@@ -31,6 +31,7 @@
 import java.security.spec.ECPrivateKeySpec;
 import java.security.spec.ECPublicKeySpec;
 import java.security.spec.EllipticCurve;
+import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
 
@@ -187,6 +188,45 @@
         isTrue(Arrays.areEqual(namedPubKey, pubKey.getEncoded()));
     }
 
+    public void testKeyFactory()
+        throws Exception
+    {
+        KeyFactory kfBc = KeyFactory.getInstance("EC", "BC");
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
+        
+        kpGen.initialize(256);
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        isTrue(kfBc.getKeySpec(kp.getPublic(), KeySpec.class) instanceof ECPublicKeySpec);
+        isTrue(kfBc.getKeySpec(kp.getPublic(), ECPublicKeySpec.class) instanceof ECPublicKeySpec);
+        isTrue(kfBc.getKeySpec(kp.getPrivate(), KeySpec.class) instanceof ECPrivateKeySpec);
+        isTrue(kfBc.getKeySpec(kp.getPrivate(), ECPrivateKeySpec.class) instanceof ECPrivateKeySpec);
+    }
+
+    private void pointCompressionTest()
+        throws Exception
+    {
+        String[] ids = new String[]{
+            "P-256",
+            "B-409",
+            "K-283"};
+
+        for (int i = 0; i != ids.length; i++)
+        {
+            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
+
+            kpGen.initialize(new ECGenParameterSpec(ids[i]));
+
+            KeyPair kp = kpGen.generateKeyPair();
+
+            byte[] enc1 = kp.getPublic().getEncoded();
+            byte[] enc2 = org.bouncycastle.jcajce.util.ECKeyUtil.createKeyWithCompression((ECPublicKey)kp.getPublic()).getEncoded();
+
+            isTrue(enc1.length >= enc2.length + 32);
+        }
+    }
+
     private void decodeTest()
     {
         EllipticCurve curve = new EllipticCurve(
@@ -1227,6 +1267,8 @@
         testSM2();
         testNonsense();
         testNamedCurveInKeyFactory();
+        testKeyFactory();
+        pointCompressionTest();
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECEncodingTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECEncodingTest.java
index 1bd3178..559cf6f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECEncodingTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ECEncodingTest.java
@@ -12,6 +12,7 @@
 import java.security.SignatureException;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
 import java.util.Date;
 
 import org.bouncycastle.asn1.ASN1InputStream;
@@ -74,6 +75,30 @@
         ks[1] = k2;
         ks[2] = k1;
     }
+
+    private void testPointCompressionProperty()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
+
+        kpGen.initialize(new ECGenParameterSpec("P-256"));
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        byte[] enc1 = kp.getPublic().getEncoded();
+
+        System.setProperty("org.bouncycastle.ec.enable_pc", "true");
+
+        byte[] enc2 = kp.getPublic().getEncoded();
+
+        isTrue(enc2.length == enc1.length - 32);
+
+        System.setProperty("org.bouncycastle.ec.enable_pc", "false");
+
+        byte[] enc3 = kp.getPublic().getEncoded();
+
+        isTrue(areEqual(enc1, enc3));
+    }
     
     public void performTest()
         throws Exception
@@ -94,6 +119,7 @@
         testParams(ecParams, false);
         
         testPointCompression();
+        testPointCompressionProperty();
     }
     
     private void testParams(byte[] ecParameterEncoded, boolean compress)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/EdECTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/EdECTest.java
index a73fd2f..4e634c5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/EdECTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/EdECTest.java
@@ -16,6 +16,8 @@
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Signature;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.InvalidKeySpecException;
@@ -27,9 +29,14 @@
 
 import javax.crypto.KeyAgreement;
 
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1Integer;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
 import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.Certificate;
+import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
 import org.bouncycastle.jcajce.spec.DHUParameterSpec;
 import org.bouncycastle.jcajce.spec.EdDSAParameterSpec;
 import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
@@ -83,22 +90,43 @@
 
         isTrue("priv failed", areEqual(privEnc, priv.getEncoded()));
 
+        isEquals(((EdDSAPrivateKey)priv).getPublicKey(), pub);
+
         priv = kFact.generatePrivate(new PKCS8EncodedKeySpec(privWithPubEnc));
 
         isTrue("priv with pub failed", areEqual(privWithPubEnc, priv.getEncoded()));
 
+        isEquals(((EdDSAPrivateKey)priv).getPublicKey(), pub);
+        
         serializationTest("ref priv", priv);
 
         Signature sig = Signature.getInstance("EDDSA", "BC");
 
-        Certificate x25519Cert = Certificate.getInstance(EdECTest.x25519Cert);
+        ASN1Sequence x25519Seq = ASN1Sequence.getInstance(EdECTest.x25519Cert);
+        Certificate x25519Cert = Certificate.getInstance(x25519Seq);
 
         sig.initVerify(pub);
 
-        sig.update(x25519Cert.getTBSCertificate().getEncoded());
+        // yes, the demo certificate is invalid...
+        sig.update(x25519Seq.getObjectAt(0).toASN1Primitive().getEncoded(ASN1Encoding.DL));
 
         isTrue(sig.verify(x25519Cert.getSignature().getBytes()));
 
+        CertificateFactory certFact = CertificateFactory.getInstance("X.509", "BC");
+
+        X509Certificate c = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(EdECTest.x25519Cert));
+
+        isTrue("Ed25519".equals(c.getSigAlgName()));
+
+        // this may look abit strange but it turn's out the Oracle CertificateFactory tampers
+        // with public key parameters on building the public key. If the keyfactory doesn't
+        // take things into account the generate throws an exception!
+        certFact = CertificateFactory.getInstance("X.509", "SUN");
+
+        c = (X509Certificate)certFact.generateCertificate(new ByteArrayInputStream(EdECTest.x25519Cert));
+
+        testPKCS8Override();
+        
         x448AgreementTest();
         x25519AgreementTest();
         ed448SignatureTest();
@@ -618,6 +646,58 @@
         isTrue(signature.verify(sig));
     }
 
+    private void testPKCS8Override()
+        throws Exception
+    {
+        System.setProperty("org.bouncycastle.pkcs8.v1_info_only", "true");
+
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EdDSA", "BC");
+
+        kpGen.initialize(448);
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        PrivateKeyInfo info = PrivateKeyInfo.getInstance(kp.getPrivate().getEncoded());
+
+        isTrue(info.getPublicKeyData() == null);
+        isTrue(info.getVersion().equals(new ASN1Integer(0)));
+
+        kpGen = KeyPairGenerator.getInstance("XDH", "BC");
+
+        kpGen.initialize(448);
+
+        kp = kpGen.generateKeyPair();
+
+        info = PrivateKeyInfo.getInstance(kp.getPrivate().getEncoded());
+
+        isTrue(info.getPublicKeyData() == null);
+        isTrue(info.getVersion().equals(new ASN1Integer(0)));
+
+        System.setProperty("org.bouncycastle.pkcs8.v1_info_only", "false");
+
+        kpGen = KeyPairGenerator.getInstance("EdDSA", "BC");
+
+        kpGen.initialize(448);
+
+        kp = kpGen.generateKeyPair();
+
+        info = PrivateKeyInfo.getInstance(kp.getPrivate().getEncoded());
+
+        isTrue(info.getPublicKeyData() != null);
+        isTrue(info.getVersion().equals(new ASN1Integer(1)));
+
+        kpGen = KeyPairGenerator.getInstance("XDH", "BC");
+
+        kpGen.initialize(448);
+
+        kp = kpGen.generateKeyPair();
+
+        info = PrivateKeyInfo.getInstance(kp.getPrivate().getEncoded());
+
+        isTrue(info.getPublicKeyData() != null);
+        isTrue(info.getVersion().equals(new ASN1Integer(1)));
+    }
+
     public static void main(
         String[] args)
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/FIPSDESTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/FIPSDESTest.java
index 5a59987..80024d4 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/FIPSDESTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/FIPSDESTest.java
@@ -23,7 +23,7 @@
 
 /**
  * basic FIPS test class for a block cipher, just to make sure ECB/CBC/OFB/CFB are behaving
- * correctly. Tests from <a href=http://www.itl.nist.gov/fipspubs/fip81.htm>FIPS 81</a>.
+ * correctly. Tests from <a href=https://www.itl.nist.gov/fipspubs/fip81.htm>FIPS 81</a>.
  */
 public class FIPSDESTest
     implements Test
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/GOST3412Test.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/GOST3412Test.java
index cbb199e..cc48b2a 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/GOST3412Test.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/GOST3412Test.java
@@ -3,18 +3,23 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.Key;
 import java.security.Security;
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
 import javax.crypto.CipherOutputStream;
+import javax.crypto.KeyGenerator;
 import javax.crypto.Mac;
+import javax.crypto.SecretKey;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -163,6 +168,50 @@
         }
     }
 
+    private void testCTR()
+        throws Exception
+    {
+        testG3413CTRInit(8);
+
+        try
+        {
+            testG3413CTRInit(16);
+        }
+        catch (InvalidAlgorithmParameterException e)
+        {
+            isTrue(e.getMessage().endsWith("IV must be 8 bytes long."));
+        }
+    }
+
+    private void testG3413CTRInit(final int pIVLen)
+        throws Exception
+    {
+        /* Create the generator and generate a key */
+        KeyGenerator myGenerator = KeyGenerator.getInstance("GOST3412-2015", "BC");
+
+        /* Initialise the generator */
+        myGenerator.init(256);
+        SecretKey myKey = myGenerator.generateKey();
+
+        /* Create IV */
+        byte[] myIV = new byte[pIVLen];
+        CryptoServicesRegistrar.getSecureRandom().nextBytes(myIV);
+
+        /* Create a G3413CTR Cipher */
+        Cipher myCipher = Cipher.getInstance("GOST3412-2015" + "/CTR/NoPadding", "BC");
+        myCipher.init(Cipher.ENCRYPT_MODE, myKey, new IvParameterSpec(myIV));
+
+        byte[] msg = Strings.toByteArray("G3413CTR JCA init Bug fixed");
+
+        byte[] enc = myCipher.doFinal(msg);
+        
+        myCipher.init(Cipher.DECRYPT_MODE, myKey, new IvParameterSpec(myIV));
+
+        byte[] dec = myCipher.doFinal(enc);
+
+        isTrue(areEqual(msg, dec));
+    }
+
     public void performTest()
         throws Exception
     {
@@ -193,6 +242,8 @@
         {
             fail("mac test failed.");
         }
+
+        testCTR();
     }
 
     public static void main(
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/HMacTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/HMacTest.java
index 4df0c56..bddf60c 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/HMacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/HMacTest.java
@@ -13,6 +13,7 @@
 import javax.crypto.spec.RC5ParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
 import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -63,6 +64,7 @@
     static byte[] outputDSTU7564_256 = Hex.decode("98ac67aa21eaf6e8666fb748d66cfc15d5d66f5194c87fffa647e406d3375cdb");
     static byte[] outputDSTU7564_384 = Hex.decode("4e46a87e70fcd2ccfb4433a8eaec68991a96b11085c5d5484db71af51bac469c03f76e1f721843c8e8667708fe41a48d");
     static byte[] outputDSTU7564_512 = Hex.decode("5b7acf633a7551b8410fa66a60c74a494e46a87e70fcd2ccfb4433a8eaec68991a96b11085c5d5484db71af51bac469c03f76e1f721843c8e8667708fe41a48d");
+    static byte[] outputSM3 = Hex.decode("51b00d1fb49832bfb01c3ce27848e59f871d9ba938dc563b338ca964755cce70");
 
     public HMacTest()
     {
@@ -244,6 +246,7 @@
         testHMac("HMac/SHA3-512", 512, outputSha3_512);
         testHMac("HMac/GOST3411-2012-256", 256, outputGost2012_256);
         testHMac("HMac/GOST3411-2012-512", 512, outputGost2012_512);
+        testHMac("HMac/SM3", 256, outputSM3);
 
         testHMac(PKCSObjectIdentifiers.id_hmacWithSHA1.getId(), 160, output1);
         testHMac(PKCSObjectIdentifiers.id_hmacWithSHA224.getId(), 224, output224);
@@ -267,6 +270,8 @@
         testHMac(UAObjectIdentifiers.dstu7564mac_384.getId(), 384, outputDSTU7564_384);
         testHMac(UAObjectIdentifiers.dstu7564mac_512.getId(), 512, outputDSTU7564_512);
 
+        testHMac(GMObjectIdentifiers.hmac_sm3.getId(), 256, outputSM3);
+
         // test for compatibility with broken HMac.
         testHMac("OldHMacSHA384", outputOld384);
         testHMac("OldHMacSHA512", outputOld512);
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/MacTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/MacTest.java
index d011eb7..4ec9375 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/MacTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/MacTest.java
@@ -13,8 +13,8 @@
 
 /**
  * MAC tester - vectors from 
- * <a href=http://www.itl.nist.gov/fipspubs/fip81.htm>FIP 81</a> and 
- * <a href=http://www.itl.nist.gov/fipspubs/fip113.htm>FIP 113</a>.
+ * <a href=https://www.itl.nist.gov/fipspubs/fip81.htm>FIP 81</a> and 
+ * <a href=https://www.itl.nist.gov/fipspubs/fip113.htm>FIP 113</a>.
  */
 public class MacTest
     extends SimpleTest
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NISTCertPathTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NISTCertPathTest.java
index 43f90a1..b1030f4 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NISTCertPathTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NISTCertPathTest.java
@@ -26,7 +26,7 @@
 
 /*
  * These tests are taken from the NIST X.509 Validation Test Suite
- * available at: http://csrc.nist.gov/pki/testing/x509paths.html
+ * available at: https://csrc.nist.gov/pki/testing/x509paths.html
  * 
  * Only the relevant certificate and crl data has been kept, in order
  * to keep the class size to a minimum.
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
index 076dfc3..f2359a7 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/NetscapeCertRequestTest.java
@@ -1,14 +1,13 @@
 package org.bouncycastle.jce.provider.test;
 
 import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.Security;
 
+import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEROutputStream;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.jce.netscape.NetscapeCertRequest;
@@ -68,15 +67,11 @@
 
             nscr.setPublicKey (kp.getPublic());
             nscr.sign (kp.getPrivate());
-            
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            DEROutputStream deros = new DEROutputStream (baos);
-            deros.writeObject (nscr);
-            deros.close();
 
-            
+            byte[] derEncoding = nscr.getEncoded(ASN1Encoding.DER);
+
             ASN1InputStream     in2 =
-                new ASN1InputStream (new ByteArrayInputStream(baos.toByteArray()));
+                new ASN1InputStream (new ByteArrayInputStream(derEncoding));
             ASN1Sequence        spkac2 = (ASN1Sequence)in2.readObject ();
 
             // System.out.println("SPKAC2: \n"+DERDump.dumpAsString (spkac2));
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/OpenSSHSpecTests.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/OpenSSHSpecTests.java
index 76d8789..e7707de 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/OpenSSHSpecTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/OpenSSHSpecTests.java
@@ -7,9 +7,9 @@
 import java.security.SecureRandom;
 import java.security.Security;
 
+import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
+import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.jce.spec.OpenSSHPrivateKeySpec;
-import org.bouncycastle.jce.spec.OpenSSHPublicKeySpec;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
@@ -181,9 +181,12 @@
 
         isTrue("EDPublic key not same", Arrays.areEqual(rawPub, edDsaPublicKeySpec.getEncoded()));
 
-        isTrue("EDPrivate key not same", Arrays.areEqual(
-            Hex.decode("6f70656e7373682d6b65792d763100000000046e6f6e65000000046e6f6e650000000000000001000000330000000b7373682d6564323535313900000020ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c5850000008300ff00ff00ff00ff0000000b7373682d6564323535313900000020ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c58500000040f80531de477603ec2150aaeb33b5f2f92be6120f899118b0706fb8c7897c4824ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c58500000000"),
-            edDsaPrivateKeySpec.getEncoded()));
+        // EdEc private keys include a random check int, so we check around it.
+        byte[] enc = edDsaPrivateKeySpec.getEncoded();
+        byte[] base = Hex.decode("6f70656e7373682d6b65792d763100000000046e6f6e65000000046e6f6e650000000000000001000000330000000b7373682d6564323535313900000020ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c58500000088");
+        byte[] tail = Hex.decode("0000000b7373682d6564323535313900000020ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c58500000040f80531de477603ec2150aaeb33b5f2f92be6120f899118b0706fb8c7897c4824ce02695ed641ccb4961b7485c9605dfe0e972bf3befd9f9d4bd0dc9f0862c585000000000102030405");
+        isTrue("EDPrivate key base not same", Arrays.areEqual(base, Arrays.copyOfRange(enc, 0, base.length)));
+        isTrue("EDPrivate key tail not same", Arrays.areEqual(tail, Arrays.copyOfRange(enc, base.length + 8, enc.length)));
 
         isEquals("ssh-ed25519", edDsaPublicKeySpec.getType());
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PBETest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PBETest.java
index 0dfbbad..dfc292f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PBETest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PBETest.java
@@ -302,6 +302,7 @@
     private byte[] hMac1 = Hex.decode("bcc42174ccb04f425d9a5c8c4a95d6fd7c372911");
     private byte[] hMac2 = Hex.decode("cb1d8bdb6aca9e3fa8980d6eb41ab28a7eb2cfd6");
     private byte[] hMac3 = Hex.decode("514aa173a302c770689269aac08eb8698e5879ac");
+    private byte[] hMac4 = Hex.decode("d24b4eb0e5bd611d4ca88bd6428d14ee2e004c7e");
 
     private Cipher makePBECipherUsingParam(
         String  algorithm,
@@ -437,8 +438,6 @@
             SecretKeyFactory    fact = SecretKeyFactory.getInstance(hmacName, "BC");
 
             key = fact.generateSecret(new PBEKeySpec("hello".toCharArray(), new byte[20], 100, 160));
-
-            mac = Mac.getInstance("HMAC-SHA1", "BC");
         }
         catch (Exception e)
         {
@@ -448,6 +447,8 @@
 
         try
         {
+            mac = Mac.getInstance("HMAC-SHA1", "BC");
+
             mac.init(key);
         }
         catch (Exception e)
@@ -673,6 +674,7 @@
         testPBEHMac("PBEWithHMacRIPEMD160", hMac2);
 
         testPBEonSecretKeyHmac("PBKDF2WithHmacSHA1", hMac3);
+        testPBEonSecretKeyHmac("PBKDF2WithHMacSM3", hMac4);
 
         testCipherNameWithWrap("PBEWITHSHA256AND128BITAES-CBC-BC", "AES/CBC/PKCS5Padding");
         testCipherNameWithWrap("PBEWITHSHAAND40BITRC4", "RC4");
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StorePBETest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StorePBETest.java
new file mode 100644
index 0000000..074111a
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StorePBETest.java
@@ -0,0 +1,127 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.io.ByteArrayInputStream;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.Security;
+import java.util.Enumeration;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Base64;
+import org.bouncycastle.util.test.SimpleTest;
+
+// Test for "PBE" dependency issue in Oracle PKCS12.
+public class PKCS12StorePBETest
+    extends SimpleTest
+{
+    private static byte[] ks = Base64.decode(
+        "MIIOcQIBAzCCDioGCSqGSIb3DQEHAaCCDhsEgg4XMIIOEzCCCGcGCSqGSIb3"
+      + "DQEHAaCCCFgEgghUMIIIUDCCAwAGCyqGSIb3DQEMCgECoIICszCCAq8wKQYK"
+      + "KoZIhvcNAQwBAzAbBBSg3fagmuDIj1QVM1ub+2lAKSeOZQIDAMNQBIICgEGJ"
+      + "lN9dRh6IzqXS1b0lhaqBKUu4TbXejx36//jX0aJX2upiAXqDEh8s21Z/y0hB"
+      + "/XWGl0IY1pjVFG6xI0c66Tk3SJRhBN88K3EVXz7URYFackI+3IPKXnWHS822"
+      + "kssWiBhVnwXokKSBMqcqgZJAlI6E2Uw1EKYHtJIhxuLEy0huQPdRmd906lBa"
+      + "GNGup+Ek5av1YZm4FvbiflH03u4X4nqn0+GCJ16S9HiAIhSE7P295z3ObWpm"
+      + "rXFFjExOsE2bIRqWaGBT2GpMADvfQeMKnoQ+7ptUPIb+wxpdbMhgzPJQGByy"
+      + "V8MxfzNew64it0s0qtXjY+q3n6yEmn/Rtf5uqs8OnM7AqpMv9zUzP/vDJ4Fk"
+      + "H2svb9uGCXCdmkuD3lvZltYi00kJH7Hdwi+W3am6PdIT3pSK4c6AuSqghBVw"
+      + "sKVrvs50DOCIOESjqSpvBESpXp0ouxeAKZ4CRPIHrQGPA1pJEM33ZvsegrUi"
+      + "NhsAttTGD8mValnRLrhc1bxKMdHwERUUvRk7webXJbRE8gpXhjgfDt49WeGd"
+      + "QipUWzw2ypTPoaiLuxVGFuGIlfqWODcOzVYbzWyzzGMtTOFZvdd8UHurci55"
+      + "AhVcw8bxl29YLUGE79kqnOZaOr9Jbnr/0X9XJs9S/Q66fEmgq0Fb23a6UDhH"
+      + "1IbREVNRBn0nOUPYbxc0te3C4av1eQKqMjt8vshxe9s25it2U7web9sJe811"
+      + "nGvMj4CVWpOPBJR/SsqccmrXM/UinsFDykAzmC/JUBDlDm7mtr9gjVrSUksz"
+      + "UtiB+DW7CwGpN+jhX/ulDvSTomlp1qTWnF16EUnyuKPrq5GNqBhx7l+4eKlD"
+      + "mhOU8w9uW8YxOjAVBgkqhkiG9w0BCRQxCB4GAGIAYQByMCEGCSqGSIb3DQEJ"
+      + "FTEUBBJUaW1lIDE1NjgxMTc2MDU2MzMwggVIBgsqhkiG9w0BDAoBAqCCBPsw"
+      + "ggT3MCkGCiqGSIb3DQEMAQMwGwQURfaj4m7iaZG6/EHEDw1xQEgokOwCAwDD"
+      + "UASCBMiJCZVMEHna9WrBqpe11jfx0NiKozkOzTgLZjK6sHGadTN0+KwkuGAW"
+      + "Ae0yAU8xkSWORI1cnEffvditacHj2tNtNaADvKwClNaRt4wkprc4kJX3pJwB"
+      + "HwZpBfewcRv4EO+xMLhcn57mFcsP90RHAPhoMhdOh0gID3XbzN40/UHkAH9Q"
+      + "TOSChCmCyMXp4lOj2AABaUPgtrrEZejrXiBQrRxZCfKpZ61shgD+P3qEffDs"
+      + "EdgsdFDxAD1JxICESPvXdAKx6qj9Hz1x42HwKBncD96qqiIFL1Ph/fUUktvP"
+      + "I4pQkUx4/ppgYgYeUNCL01FzsOzvMvGucHaIu3vCKRvzmKl12GHpP+RroDHM"
+      + "UbZQQKijYZ+MLMqzDuV0MhmlwIIXBwrWs+AyP3vpBLlx/wC1sPATZA8kxbbU"
+      + "sTLXLmmaM8A24YlDcXKgO2gmQlngkVgy+8uBOhjNL1C50m+Rssar5Wa/g9tJ"
+      + "1s/VIDqSprBYlzVrtgFDa0zXaTvVmy5ej5+wL+TUIZUjv3zl9M/w/e+/u7Qb"
+      + "w5G1kWnB5UTdnkrhdrut0Z5830CO5yo5bNKwR8OjchOpewr8ZdsHrK6iq2QO"
+      + "7IrEYPrxjc1f8g0QBXKM/w7ATYtlePHL3w60MpYU9m6gjMntXy5ooebl0O//"
+      + "WZTJhiOd48g7iSZpmMruRTNXTH32UiU+ovwWcoHYEAY4f4iNjikpKh2wRz55"
+      + "uX490BEXzmTafKRfdo30+8sr0I5S3TD0Vk00NJNEC2G7v4uuMsoIlNGuRveE"
+      + "lV+Qpjv4vtqRoJtXklEtzvF90Zfoh1N8u+XZ9eVTPCbSfS3jwXAtiISWz3cF"
+      + "S8VA+v04p5Dic294oFWW3ClyB+MhqniINxDP6htMnfnvaZNOni0gqtQFXylI"
+      + "o0HxhlgK9JeFyAEXuxU6GtIEYq2v1RdY3zI+aUSLNEw9IY0z6FsrJSiKiUA5"
+      + "o6InIRDaEdCCDFm50wdZCRXyrKtLw+cnpiFna/QFgscMqw6hQEthizdFjtsa"
+      + "SzTy0fMoU62Ir3NSKR3OJIL2uqU4MzRteUSY+DPkqHOe95bqLajCWTyTgEx9"
+      + "LWVbQ0r9ObXcyyZsJWpLxy0Wqd0ZTThEqrJvgqPvL1phFA40T/eOxuqcDqGK"
+      + "zlMvZEUSDRDHiA7wroc8wpYZ9UtT1B9/V7qPQBDK/zH3lL5nniNH/+/4aZb6"
+      + "cyTaMTh8swZcEuxBAeYJFnqSv5KSx5GpUnhlwPe2llfQC8q8i0xoEE5gp0dF"
+      + "95GIJycaHJ04tGeeJavqr+81G9p1HptLrh+cIGRUOfkqBpXstxALy7HyvOC5"
+      + "/stwVc/XH7rbndyGUlLG39VNEbFqcJnVtK5ufm+nQc4jLvjr+cIKLenkclDM"
+      + "Anp61fF5btiwoqwBmkfRI/OQsX7gQSdBi/u0bqa9da8xafPZPbGeY6K0wML1"
+      + "Ekq1ZhL2EIeoiv2BELSLMZG8r8WVM4P47XlOwGgKc44zjjSO19xtstKoZgWM"
+      + "miDf5gPlnEHhGbMMPCw/O/XqkqE94yf5pKBl6Lq8M/Hfw7LtaTb4nn8fKERU"
+      + "0LNrY0pDTS2fkZdRj6oq6oxXAFIiBj2SQ8miv3yeIXWRqtHLiKSuPFcePsjp"
+      + "f0+BDBIx6GgyTaHejmsxOjAVBgkqhkiG9w0BCRQxCB4GAGYAbwBvMCEGCSqG"
+      + "SIb3DQEJFTEUBBJUaW1lIDE1NjgxMTc2MDc1MTMwggWkBgkqhkiG9w0BBwag"
+      + "ggWVMIIFkQIBADCCBYoGCSqGSIb3DQEHATApBgoqhkiG9w0BDAEGMBsEFC4x"
+      + "b5bfwfET6VjPjM+PvTk638IaAgMAw1CAggVQAHGzz7/djgJH2jCE+NLS4yPk"
+      + "/v7ZRqwpk26TD2tx4hnGvuG+CB9YBxVXRMvjrLG0dHh59o2ewgl3/+EpgaRa"
+      + "LuUIEDSySjcRueWVtluUKZQTidSGaLB5w949AowWiPj8yUeBtx+gNWUJPfaF"
+      + "SDE9iPWr5Mr3ZPapW3LDc+8hqX74or34bBjg1EH3L2mggpQlRbjXpZJJfM2+"
+      + "t7ajcQylT7PUrbW4qT0sqB5r+0EMFbkRDNMpiQ6EGanGA9ualarPA2iTJEV5"
+      + "Lz8udr6UqN8o/YuNgtAPo1PIkH1lES9XNOKkJsatzjCX2aaI7fncJiKXmBWR"
+      + "wLnoRBIw8BYIopgUcWU3sdGDeIvxQ5Be2L+701ha464B9XrIpB1g86xk3TP2"
+      + "Hs3s2ncYZt4FxQAvTKmpQJPPj22EcfrZmwZXkR10nJ+WnxCdbD2gblAswWUL"
+      + "dZR4N4Et+E8CkNWdd+gemwbDMSD3R/Zf1LKJlB0Oz9R+Rl2Qk+vJsgFErGyk"
+      + "qHR4tm0dhPGp1N+9tYsgmLl3PeX4hpoPG2ZEB/FHCYpWGjwQ3Awv9K+P+CMq"
+      + "g0CRR/ptGlM4Zj/5CfVHmm2+Gh6MCQwYiuJxGz4aRqx8DLs3dwymnbkC+E4C"
+      + "x/5wJRtnZhKxoblHjRzT3ZgjJkJGSjKzV5N/Fjxsx345zc5kv5pXsj7JTeIw"
+      + "l0WwKUDquki5eJRGHwfy+bldHv2IUUQLBImOMOcesA3YTj9y40WpzCEUfkaO"
+      + "vK9zSwS9PshmYLgHOVTi5eN1BXSMZGtoJtzw9Ez20tlzKIiJ3vmJCvhPF0kR"
+      + "Lh9Pi/vjj8BlceEo6V10haOb1EDtVvjEgzxzEhBcqMMX2L3J31bmIL0S70IE"
+      + "rqsJoPrkY7SGG/8D7d2EH+3OsgrjjnblQC21O8asV70o7+o3N2rK3gXOMv+v"
+      + "9cpnAa31zSS/E1Zc1+NR7p+JOifqdzq1XY1uY8Q3EV5o3lc2VEMTFvMFTeoX"
+      + "K5CcTQsX+8KhYiNLg5sJwaE3Pft4Q/+QkSSBc1p05aeFOBbs4mQM/Binxuun"
+      + "EtTKEH4NrRHjkF3ACRs2Rl3EnXMXlF5Uqf0uZmxMjbpEdhoU9wTqRHX3Oosi"
+      + "zTTXbi7peQB20lRLzM7HqgXO7XDi+54BeJPdn34V4+hdnO75D1rJS3WkeADy"
+      + "QXFQ5xg99e2iz6rvjNR4T9k6b9ILM8vGfgk7RLALuifHgbSuVAt1W9CwhjeU"
+      + "RrhgDQGcAJDQoB55wIAt/Ab3F0Db1y00zg1pnT1NPhc5BVl2SiO50k7VYzoq"
+      + "sQra/szqqP6pRwztu/NTxS6V6yrAQgTiN2ZScsyci8x6jtGjc+9dmMH/iLeg"
+      + "bIDFU2//v6jV6Z8G8alTl3OZgI089qUdhO01z5pU9zT4sIjnrxeql2rH6XvU"
+      + "gyRjw51tgHZ+EzDJagNxJiaUt78tLbfmMK6daNuec1PTCiCLauAYoYgnG2Ri"
+      + "kGHXDUMT8+jpM7PGuXFUXnZtzBpsdxTq7QLTcGPnQyarfpuLB4jrAu/STbxV"
+      + "ZWAzLF/IRtAvuGvhSC14CFAfEIeezUuwH8UGDlUHOfhme3b+tPp5ccbp+z98"
+      + "ebLUk03Swbzjd0H0JQE8lU/IXx25Cctl/a44ogvD78qPqa5HqgwVCvIctqNB"
+      + "sQru272ush+gOSjDimDkalwFrqb2CsxFX9EwqPdTEZYkB+ZQUIGf0h98YLua"
+      + "0PKgj7DVTYEuj9En5EFRAg6CUNi4whfCErweBTsstkL0bfljPKnOGPClFJgQ"
+      + "WTbB4v+cpt51wtjkpHnOy5yIyuE7eEAMJUj21eagzsw8IYSgCzA+MCEwCQYF"
+      + "Kw4DAhoFAAQU9RrR2YWVsI0cRNUUqCHmb9pAGQ0EFPcEHRZF1YnurK3MLCKy"
+      + "/43iJXvlAgMBhqA=");
+
+    public String getName()
+    {
+        return "PKCS12StorePBETest";
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+
+        keyStore.load(new ByteArrayInputStream(ks), "password".toCharArray());
+
+        Security.insertProviderAt(new BouncyCastleProvider(), 1);
+
+        for (Enumeration en = keyStore.aliases(); en.hasMoreElements();)
+        {
+            Key key = keyStore.getKey((String)en.nextElement(), "password".toCharArray());
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        runTest(new PKCS12StorePBETest());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
index b8b1e74..45d2027 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKCS12StoreTest.java
@@ -27,7 +27,7 @@
 import org.bouncycastle.asn1.ASN1StreamParser;
 import org.bouncycastle.asn1.DERBMPString;
 import org.bouncycastle.asn1.DERNull;
-import org.bouncycastle.asn1.DERSequenceParser;
+import org.bouncycastle.asn1.DLSequenceParser;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.ContentInfo;
 import org.bouncycastle.asn1.pkcs.EncryptedData;
@@ -58,6 +58,8 @@
 public class PKCS12StoreTest
     extends SimpleTest
 {
+    private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
+    
     static char[]   passwd = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' };
 
     //
@@ -553,6 +555,24 @@
       + "MDEwITAJBgUrDgMCGgUABBT3iAwuHw7KQXrl09gBkHaUVbOoBAQIIm90qua1"
       + "2i4CAggA");
 
+    private static byte[] certsOnly = Base64.decode(
+        "MIICnwIBAzCCApgGCSqGSIb3DQEHAaCCAokEggKFMIICgTCCAn0GCSqGSIb3" +
+            "DQEHAaCCAm4EggJqMIICZjCCAmIGCyqGSIb3DQEMCgEDoIICHDCCAhgGCiq" +
+            "GSIb3DQEJFgGgggIIBIICBDCCAgAwggFpoAMCAQICBHcheqIwDQYJKoZIhv" +
+            "cNAQELBQAwMjENMAsGA1UEChMERGVtbzENMAsGA1UECxMERGVtbzESMBAGA" +
+            "1UEAxMJRGVtbyBjZXJ0MCAXDTE5MDgzMTEzMDgzNloYDzIxMDkwNTE5MTMw" +
+            "ODM2WjAyMQ0wCwYDVQQKEwREZW1vMQ0wCwYDVQQLEwREZW1vMRIwEAYDVQQ" +
+            "DEwlEZW1vIGNlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKOVC4" +
+            "Qeg0KPAPRB9WcZdvXitiJ+E6rd3czQGNzEFC6FesAllH3PHSWuUZ2YjhiVM" +
+            "YJyzwVP1II04iCRaIc65R45oVrHZ2ybWAOda2hBtySjQ2pIQQpoKE7nvL3j" +
+            "JcHoCIBJVf3c3xpfh7RucCOGiZDjU9CYPG8yznsazb5+fPF/AgMBAAGjITA" +
+            "fMB0GA1UdDgQWBBR/7wUDwa7T0vNzNgjOKdjz2Up9RzANBgkqhkiG9w0BAQ" +
+            "sFAAOBgQADzPFsaLhVYD/k9qMueYKi8Ftwijr37niF98cgAHEtq6TGsh3Se" +
+            "8gEK3dNJL18vm7NXgGsl8jUWsE9hCF9ar+/cDZ+KrZlZ5PLfifXJJKFqVAh" +
+            "sOORef0NRIVcTCoyQTW4pNpNZP9Ul5LJ3iIDjafgJMyEkRbavqdyfSqVTvY" +
+            "NpjEzMBkGCSqGSIb3DQEJFDEMHgoAYQBsAGkAYQBzMBYGDGCGSAGG+Watyn" +
+            "sBATEGBgRVHSUA");
+
     /**
      * we generate a self signed certificate for the sake of testing - RSA
      */
@@ -585,19 +605,54 @@
         return TestUtils.createCert(issuerBldr.build(), privKey, subjectBldr.build(), "SHA1withRSA", null, pubKey);
     }
 
+    private void testCertsOnly()
+        throws Exception
+    {
+        KeyStore pkcs12 = KeyStore.getInstance("PKCS12", BC);
+
+        pkcs12.load(new ByteArrayInputStream(certsOnly), null);
+
+        isTrue(pkcs12.containsAlias("alias"));
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        pkcs12.store(bOut, null);
+
+        pkcs12 = KeyStore.getInstance("PKCS12", BC);
+
+        pkcs12.load(new ByteArrayInputStream(bOut.toByteArray()), null);
+
+        isTrue(pkcs12.containsAlias("alias"));
+
+        try
+        {
+            pkcs12.load(new ByteArrayInputStream(certsOnly), "1".toCharArray());
+            fail("no exception");
+        }
+        catch (IOException e)
+        {
+            isEquals("password supplied for keystore that does not require one", e.getMessage());
+        }
+
+        System.setProperty("org.bouncycastle.pkcs12.ignore_useless_passwd", "true");
+        
+        pkcs12.load(new ByteArrayInputStream(certsOnly), "1".toCharArray());
+
+        System.setProperty("org.bouncycastle.pkcs12.ignore_useless_passwd", "false");
+    }
     private void testGOSTStore()
         throws Exception
     {
         byte[] data = Hex.decode("deadbeef");
 
-        KeyStore pkcs12 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore pkcs12 = KeyStore.getInstance("PKCS12", BC);
 
         pkcs12.load(new ByteArrayInputStream(gostPfx), "1".toCharArray());
 
         PrivateKey pk = (PrivateKey)pkcs12.getKey("cp_exported", null);
         Certificate[] pubCerts = pkcs12.getCertificateChain("cp_exported");
 
-        Signature sig = Signature.getInstance("ECGOST3410", "BC");
+        Signature sig = Signature.getInstance("ECGOST3410", BC);
 
         sig.initSign(pk);
 
@@ -605,7 +660,7 @@
 
         byte[] signature = sig.sign();
 
-        sig = Signature.getInstance("ECGOST3410", "BC");
+        sig = Signature.getInstance("ECGOST3410", BC);
 
         sig.initVerify(pubCerts[0].getPublicKey());
 
@@ -616,7 +671,7 @@
             fail("key test failed in GOST store");
         }
 
-        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore ks = KeyStore.getInstance("PKCS12", BC);
 
         ks.load(new ByteArrayInputStream(gostOpenSSLIntegerDPfx), "password".toCharArray());
 
@@ -652,7 +707,7 @@
         isEquals(8, mData.getSalt().length);
 
         //confirm key recovery
-        pkcs12 = KeyStore.getInstance("PKCS12", "BC");
+        pkcs12 = KeyStore.getInstance("PKCS12", BC);
 
         pkcs12.load(new ByteArrayInputStream(stream.toByteArray()), "2".toCharArray());
 
@@ -665,7 +720,7 @@
         throws Exception
     {
         BigInteger  mod = new BigInteger("bb1be8074e4787a8d77967f1575ef72dd7582f9b3347724413c021beafad8f32dba5168e280cbf284df722283dad2fd4abc750e3d6487c2942064e2d8d80641aa5866d1f6f1f83eec26b9b46fecb3b1c9856a303148a5cc899c642fb16f3d9d72f52526c751dc81622c420c82e2cfda70fe8d13f16cc7d6a613a5b2a2b5894d1", 16);
-        KeyStore store = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore store = KeyStore.getInstance("PKCS12", BC);
         ByteArrayInputStream stream = new ByteArrayInputStream(pkcs12);
 
         store.load(stream, passwd);
@@ -763,7 +818,7 @@
         }
 
         ASN1Encodable outer = new ASN1StreamParser(data).readObject();
-        if (!(outer instanceof DERSequenceParser))
+        if (!(outer instanceof DLSequenceParser))
         {
             fail("Failed DER encoding test.");
         }
@@ -791,7 +846,7 @@
         }
 
         outer = new ASN1StreamParser(data).readObject();
-        if (!(outer instanceof DERSequenceParser))
+        if (!(outer instanceof DLSequenceParser))
         {
             fail("Failed DER encoding test.");
         }
@@ -821,7 +876,7 @@
         }
 
         outer = new ASN1StreamParser(data).readObject();
-        if (!(outer instanceof DERSequenceParser))
+        if (!(outer instanceof DLSequenceParser))
         {
             fail("Failed DER encoding test.");
         }
@@ -848,7 +903,7 @@
         //
         // UTF 8 single cert test
         //
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
         stream = new ByteArrayInputStream(certUTF);
 
         store.load(stream, "user".toCharArray());
@@ -883,7 +938,7 @@
 
         try
         {
-            KeyFactory  fact = KeyFactory.getInstance("RSA", "BC");
+            KeyFactory  fact = KeyFactory.getInstance("RSA", BC);
 
             privKey = fact.generatePrivate(privKeySpec);
             pubKey = fact.generatePublic(pubKeySpec);
@@ -899,7 +954,7 @@
 
         testSupportedTypes(privKey, chain);
 
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
 
         store.load(null, null);
 
@@ -934,7 +989,7 @@
         //
         // no friendly name test
         //
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
         stream = new ByteArrayInputStream(pkcs12noFriendly);
 
         store.load(stream, noFriendlyPassword);
@@ -978,7 +1033,7 @@
         //
         // storage test
         //
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
         stream = new ByteArrayInputStream(pkcs12StorageIssue);
 
         store.load(stream, storagePassword);
@@ -1047,7 +1102,7 @@
         //
         // test restoring of a certificate with private key originally as a ca certificate
         //
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
         
         store.load(null, null);
         
@@ -1086,7 +1141,7 @@
         //
         // test of reading incorrect zero-length encoding
         //
-        store = KeyStore.getInstance("PKCS12", "BC");
+        store = KeyStore.getInstance("PKCS12", BC);
         stream = new ByteArrayInputStream(pkcs12nopass);
         
         store.load(stream, "".toCharArray());
@@ -1109,7 +1164,7 @@
     private void basicStoreTest(PrivateKey privKey, Certificate[] chain, String type)
         throws Exception
     {
-        KeyStore store = KeyStore.getInstance(type, "BC");
+        KeyStore store = KeyStore.getInstance(type, BC);
 
         store.load(null, null);
 
@@ -1234,17 +1289,17 @@
     private void testNoExtraLocalKeyID(byte[] store1data)
         throws Exception
     {
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", BC);
 
         kpg.initialize(512);
 
         KeyPair newPair = kpg.genKeyPair();
 
-        KeyStore store1 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore store1 = KeyStore.getInstance("PKCS12", BC);
 
         store1.load(new ByteArrayInputStream(store1data), passwd);
 
-        KeyStore store2 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore store2 = KeyStore.getInstance("PKCS12", BC);
 
         store2.load(null, null);
         
@@ -1281,7 +1336,7 @@
     private void testChainCycle()
         throws Exception
     {
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         // initialize key store
         keyStore.load(new ByteArrayInputStream(certChainCycle), "test".toCharArray());
@@ -1315,7 +1370,7 @@
         KeyPair kp3 = TestUtils.generateRSAKeyPair();
         X509Certificate kp3Root = TestUtils.generateRootCert(kp3, new X500Name("CN=KP3 ROOT"));
 
-        KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore keyStore = KeyStore.getInstance("PKCS12", BC);
 
         keyStore.load(null, null);
 
@@ -1331,7 +1386,7 @@
 
         byte[] baseData = bOut.toByteArray();
 
-        KeyStore ks1 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore ks1 = KeyStore.getInstance("PKCS12", BC);
 
         ks1.load(new ByteArrayInputStream(baseData), "fred".toCharArray());
 
@@ -1346,7 +1401,7 @@
 
         ks1.store(bOut1, "fred".toCharArray());
 
-        KeyStore ks2 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore ks2 = KeyStore.getInstance("PKCS12", BC);
 
         ks2.load(new ByteArrayInputStream(bOut1.toByteArray()), "fred".toCharArray());
 
@@ -1376,7 +1431,7 @@
 
         ks2.store(bOut2, "fred".toCharArray());
 
-        KeyStore ks3 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore ks3 = KeyStore.getInstance("PKCS12", BC);
 
         ks3.load(new ByteArrayInputStream(bOut2.toByteArray()), "fred".toCharArray());
 
@@ -1402,7 +1457,7 @@
         System.setProperty("org.bouncycastle.pkcs12.max_it_count", "10");
 
         ByteArrayInputStream stream = new ByteArrayInputStream(pkcs12StorageIssue);
-        KeyStore store = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore store = KeyStore.getInstance("PKCS12", BC);
 
         try
         {
@@ -1420,7 +1475,7 @@
     private void testBCFKSLoad()
         throws Exception
     {
-        KeyStore k = KeyStore.getInstance("BCFKS", "BC");
+        KeyStore k = KeyStore.getInstance("BCFKS", BC);
 
         try
         {
@@ -1454,7 +1509,7 @@
         KeyPair kp3 = TestUtils.generateRSAKeyPair();
         X509Certificate kp3Root = TestUtils.generateRootCert(kp3, new X500Name("CN=KP3 ROOT"));
 
-        KeyStore keyStore = KeyStore.getInstance("BCFKS", "BC");
+        KeyStore keyStore = KeyStore.getInstance("BCFKS", BC);
 
         keyStore.load(null, null);
 
@@ -1468,7 +1523,7 @@
 
         keyStore.store(bOut, "fred".toCharArray());
 
-        KeyStore k12 = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore k12 = KeyStore.getInstance("PKCS12", BC);
 
         try
         {
@@ -1493,25 +1548,26 @@
         testGOSTStore();
         testChainCycle();
         testBCFKSLoad();
+        testCertsOnly();
 
         // converter tests
 
-        KeyStore kS = KeyStore.getInstance("PKCS12", "BC");
+        KeyStore kS = KeyStore.getInstance("PKCS12", BC);
 
         byte[] data = PKCS12Util.convertToDefiniteLength(pkcs12);
         kS.load(new ByteArrayInputStream(data), passwd);     // check MAC
 
         ASN1Encodable obj = new ASN1StreamParser(data).readObject();
-        if (!(obj instanceof DERSequenceParser))
+        if (!(obj instanceof DLSequenceParser))
         {
             fail("Failed DER conversion test.");
         }
 
-        data = PKCS12Util.convertToDefiniteLength(pkcs12, passwd, "BC");
+        data = PKCS12Util.convertToDefiniteLength(pkcs12, passwd, BC);
         kS.load(new ByteArrayInputStream(data), passwd); //check MAC
 
         obj = new ASN1StreamParser(data).readObject();
-        if (!(obj instanceof DERSequenceParser))
+        if (!(obj instanceof DLSequenceParser))
         {
             fail("Failed deep DER conversion test - outer.");
         }
@@ -1519,7 +1575,7 @@
         Pfx pfx = Pfx.getInstance(obj);
 
         obj = new ASN1StreamParser(ASN1OctetString.getInstance(pfx.getAuthSafe().getContent()).getOctets()).readObject();
-        if (!(obj instanceof DERSequenceParser))
+        if (!(obj instanceof DLSequenceParser))
         {
             fail("Failed deep DER conversion test - inner.");
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
index df8e8b5..36a55a3 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXNameConstraintsTest.java
@@ -1,8 +1,13 @@
 package org.bouncycastle.jce.provider.test;
 
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.RFC4519Style;
 import org.bouncycastle.asn1.x509.GeneralName;
 import org.bouncycastle.asn1.x509.GeneralSubtree;
+import org.bouncycastle.asn1.x509.OtherName;
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidator;
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidatorException;
 import org.bouncycastle.util.test.SimpleTest;
@@ -81,17 +86,21 @@
     private final static String[] dnIntersection =
     { "O=test org, OU=test org unit, CN=John Doe" };
 
+    // Note: In BC text conversion is ISO format - IETF starts at the back.
     private final static String testDN = "O=test org, OU=test org unit, CN=John Doe";
 
     private final static String testDNIsConstraint[] =
-    { "O=test org, OU=test org unit",
-            "O=test org, OU=test org unit, CN=John Doe" };
+    {
+        "O=test org, OU=test org unit",
+        "O=test org, OU=test org unit, CN=John Doe",
+    };
 
     private final static String testDNIsNotConstraint[] =
-    { "O=test org, OU=test org unit, CN=John Doe2",
-            "O=test org, OU=test org unit2",
-            "OU=test org unit, O=test org, CN=John Doe",
-            "O=test org, OU=test org unit, CN=John Doe, L=USA" };
+    {
+        "O=test org, OU=test org unit, CN=John Doe2",
+        "O=test org, OU=test org unit2",
+        "O=test org, OU=test org unit, CN=John Doe, L=USA"
+    };
 
     private final static String testDNS = "abc.test.com";
 
@@ -214,6 +223,42 @@
             uriintersect);
         testConstraints(GeneralName.iPAddress, testIP, testIPIsConstraint,
             testIPIsNotConstraint, ip1, ip2, ipunion, ipintersect);
+
+        PKIXNameConstraintValidator constraintValidator = new PKIXNameConstraintValidator();
+        constraintValidator.intersectPermittedSubtree(new GeneralSubtree(
+            new GeneralName(GeneralName.directoryName, new X500Name(RFC4519Style.INSTANCE, "ou=permittedSubtree1, o=Test Certificates 2011, c=US"))));
+        constraintValidator.checkPermitted(new GeneralName(GeneralName.directoryName, new X500Name(RFC4519Style.INSTANCE, "cn=Valid DN nameConstraints EE Certificate Test1, ou=permittedSubtree1, o=Test Certificates 2011, c=US")));
+
+        GeneralName name = new GeneralName(GeneralName.otherName, new OtherName(new ASN1ObjectIdentifier("1.1"), DERNull.INSTANCE));
+        GeneralSubtree subtree = new GeneralSubtree(name);
+
+        PKIXNameConstraintValidator validator = new PKIXNameConstraintValidator();
+        validator.intersectPermittedSubtree(subtree);
+
+        name = new GeneralName(GeneralName.otherName, new OtherName(new ASN1ObjectIdentifier("1.1"), DERNull.INSTANCE));
+        subtree = new GeneralSubtree(name);
+
+        validator = new PKIXNameConstraintValidator();
+        validator.intersectPermittedSubtree(subtree);
+        validator.addExcludedSubtree(subtree);
+
+        try
+        {
+            validator.checkExcluded(name);
+        }
+        catch (PKIXNameConstraintValidatorException e)
+        {
+            isEquals("OtherName is from an excluded subtree.", e.getMessage());
+        }
+
+        try
+        {
+            validator.checkPermitted(name);
+        }
+        catch (PKIXNameConstraintValidatorException e)
+        {
+            fail(e.getMessage());
+        }
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXTest.java
index 99d21b0..7e83817 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PKIXTest.java
@@ -18,7 +18,7 @@
 {
     /*
      * The following certs and crls are described in:
-     * http://www.ietf.org/internet-drafts/draft-ietf-pkix-new-part1-08.txt
+     * https://www.ietf.org/internet-drafts/draft-ietf-pkix-new-part1-08.txt
      *
      *   This section contains four examples: three certificates and a CRL.
      *   The first two certificates and the CRL comprise a minimal
@@ -150,8 +150,8 @@
         * (g)  the certificate is an end entity certificate (not a CA
         * certificate);
         * (h)  the certificate includes an alternative subject name of
-     *    "<http://www.itl.nist.gov/div893/staff/polk/index.html>" and an
-        * alternative issuer name of "<http://www.nist.gov/>" - both are URLs;
+     *    "<https://www.itl.nist.gov/div893/staff/polk/index.html>" and an
+        * alternative issuer name of "<https://www.nist.gov/>" - both are URLs;
         * (i)  the certificate include an authority key identifier extension
         * and a certificate policies extension psecifying the policy OID
         * 2.16.840.1.101.3.2.1.48.9; and
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PSSTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PSSTest.java
index 3618593..534431d 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PSSTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/PSSTest.java
@@ -17,6 +17,7 @@
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Arrays;
@@ -273,6 +274,18 @@
             fail("SHA512 signature verification failed");
         }
 
+
+        s = Signature.getInstance(PKCSObjectIdentifiers.id_RSASSA_PSS.getId(), "BC");
+
+        s.setParameter(pss.getParameterSpec(PSSParameterSpec.class));
+
+        s.initVerify(pubKey);
+        s.update(msg1a);
+        if (!s.verify(sig1c))
+        {
+            fail("SHA512 signature verification failed");
+        }
+
         SecureRandom random = new SecureRandom();
 
         // Note: PSS minimum key size determined by hash/salt lengths
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RSATest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RSATest.java
index bb30223..554496f 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RSATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RSATest.java
@@ -18,7 +18,10 @@
 import java.security.Security;
 import java.security.Signature;
 import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAKeyGenParameterSpec;
@@ -47,8 +50,11 @@
 import org.bouncycastle.asn1.x509.DigestInfo;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.jcajce.spec.OpenSSHPrivateKeySpec;
+import org.bouncycastle.jcajce.spec.OpenSSHPublicKeySpec;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -121,6 +127,9 @@
             new BigInteger("97fc25484b5a415eaa63c03e6efa8dafe9a1c8b004d9ee6e80548fefd6f2ce44ee5cb117e77e70285798f57d137566ce8ea4503b13e0f1b5ed5ca6942537c4aa96b2a395782a4cb5b58d0936e0b0fa63b1192954d39ced176d71ef32c6f42c84e2e19f9d4dd999c2151b032b97bd22aa73fd8c5bcd15a2dca4046d5acc997021", 16),
             new BigInteger("4bb8064e1eff7e9efc3c4578fcedb59ca4aef0993a8312dfdcb1b3decf458aa6650d3d0866f143cbf0d3825e9381181170a0a1651eefcd7def786b8eb356555d9fa07c85b5f5cbdd74382f1129b5e36b4166b6cc9157923699708648212c484958351fdc9cf14f218dbe7fbf7cbd93a209a4681fe23ceb44bab67d66f45d1c9d", 16));
 
+    static final byte[] pssPublicKey = Base64.decode("MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBAJqAfazeBJT/qb6j+4Xck3V07pxMcsIUr8onGlhNW0b4XI5Wwxplg1MNB5EJGvdKIdQW5krd4FE8ltefbgUCjD028ALpQtOWoaccIxpptjD6lD8NyG1lVW96Oz9U+bq2nkgWH4B1aXZhrKOGq08bZSLj7Kq7wP1OCxhgSUI9iP+ketfLApH9OjUgMZY8/HOlwavy0yGvablJQbkDef+a4cMcptSSbgEbrZejfhIGYQ2XI1DDHJl2Bx4keOyG1HgC/EV+RmCp6pjhIs+kg45CPqznJqfeWQjo5hULXhr1Pj2B3Bvo1D5eOtrdfZg2p4XXpDVMV6MEiK9qQRONJ8gGC48CAwEAAQ==");
+    static final byte[] pssPrivateKey = Base64.decode("MIIEvAIBADALBgkqhkiG9w0BAQoEggSoMIIEpAIBAAKCAQEAw0FMvJJ15M3aEwAEJToG90JRFhzVnMPw2aKD/xFXJUVuP/Iet/LoWECSokcbcwDVa3GVuQVEsU1/sMS2a5aFoB5NqvMx0vetsBC4X9t85zY5EdMKzEszXX71jzX/yl3YIiMPM0r39Bfu0H+Ixv0wnyrrol+QqkR7MklgfbP6I91FWbeubPGxmSxyQRcz1qS05mH+BXTQ3uwxCBwLo0WO1hovsOIfhFu993qCnK3Oy3jPDvauLj8Z0hIfrHCQNaIxZUcArELLirJsANfCuI4+XCYvMJxQkmrd3ciNWtQu0orSQtWaIVccT1FezOUrme+6tRpELS5VOxearj059qrLIwIDAQABAoIBAG02bCaZwUmefpjcDHWKFHVe6Z31uOG7k08YIL6dw2G8iSNJWTdIrf8W9y2/mjHkSHuVh8p6kOafU4nbLbHV+p4J9SVma/r1wHfXkllDmoR1Bszaf5KviWaFafKVoKJfhVHqzEjDaRdl/5UtkKLE4dpVloE29OLX9RS2iDsnXQWLdyg8ni1U7bjt3twV8sB/8YNx3aQ9rOb051FsKwBocoPVrw8J5A/mWk/6EvhOpf1ItotH/Sjw/w6qwl9tbEyInkBP1+L/AExlr4fipOxDImNkxfvO7ICOgbUSExvTd1uhsHdo9pkEFlvD9ilM71EnLBGOcnnUiOjyDh4wh+aasSkCgYEA6m2GiBx3rDnj+4fx7WhNyh/whvUnWCGIWJF0ctcSD8Ok5CXe8G2X+bVXUlzj3CRZBeCG5xYxsx+d4lzlrjgBidBsoeBLGcmq5v+kB3tpwZiLwyLTb33/LzT0lEjpZOqCx32n7GMeehjdtZE7eaXaGL9IJUjsYkwn6vMLIO+oaS8CgYEA1Tj4z0GkglpOemkw52+nkj9iMr3mdGYGmtSWpsu+fFbq6Fslk+gP5N6nDBzxbKadzqSFBNgNkZtm+/abZqql9rTxOC2guYuFS7a4Wjj1e5gYCNcE/EOIR0wbhO6zlqxuuqcqqWRyz9hPUZC662S4dIMxwvwXv5780oBMR2QVWE0CgYEA5swR6Sttvsf35om+62cHPvoXCieOJrxMyjXaGb4YcCDD1EJcrQSY3SVl5RbC1teKNbkJ17UIFTwJavTew5ksGoxyhySVi7v6YBZLXXppckpHP0SoOVooxEc0jFEER3CCdPkHPDmRpc+ZZ8qmbWuVv0uDMgILh/NGUZAa4sBQY80CgYEAgibYmYp0JK2DIe170ImzO+48vsR0G7D7bx89JmtPxw43LcYVVgddTFMsnJQ+OhgqU6zRFXfcMHkvj7WkfjLEQ6eHZsdTSG8F2oWaWlhSYDMi2KKHhISkdwDZ+3bJYLu4i27m96c8/eoH4L37mxxMC7LZeS/wPyOJJ+TwqtNIxDECgYAf52wVSjHEo9WwsCet4MJhWukLMBkyO2J9IIwsGGP5MyOTpqZN3OfL0g0Qe6oSbExafszuI5DBJNNBJAQqRC6fdhlqLNJEmgKPthbhi/pmqmfcCvbqt4UcEcDdhrrSLMK+4maZMQj7JqaSa+Tv2TYK+pK6D9BuXJRPsTiqBMkJZw==");
+
     public void performTest()
         throws Exception
     {
@@ -150,6 +159,29 @@
         PrivateKey  priv2048Key = fact.generatePrivate(priv2048KeySpec);
         PublicKey   pub2048Key = fact.generatePublic(pub2048KeySpec);
 
+        // getKeySpec tests
+        isTrue(fact.getKeySpec(privKey, KeySpec.class) instanceof RSAPrivateCrtKeySpec);
+        isTrue(fact.getKeySpec(privKey, RSAPrivateKeySpec.class) instanceof RSAPrivateCrtKeySpec);
+        isTrue(fact.getKeySpec(privKey, RSAPrivateCrtKeySpec.class) instanceof RSAPrivateCrtKeySpec);
+        isTrue(fact.getKeySpec(privKey, OpenSSHPrivateKeySpec.class) instanceof OpenSSHPrivateKeySpec);
+
+        RSAPrivateKey simpPriv = (RSAPrivateKey)fact.generatePrivate(new RSAPrivateKeySpec(privKeySpec.getPrivateExponent(), privKeySpec.getModulus()));
+
+        isTrue(fact.getKeySpec(simpPriv, KeySpec.class) instanceof RSAPrivateKeySpec);
+        isTrue(fact.getKeySpec(simpPriv, RSAPrivateKeySpec.class) instanceof RSAPrivateKeySpec);
+        try
+        {
+            fact.getKeySpec(simpPriv, RSAPrivateCrtKeySpec.class);
+        }
+        catch (InvalidKeySpecException e)
+        {
+            // ignore
+        }
+
+        isTrue(fact.getKeySpec(pubKey, KeySpec.class) instanceof RSAPublicKeySpec);
+        isTrue(fact.getKeySpec(pubKey, RSAPublicKeySpec.class) instanceof RSAPublicKeySpec);
+        isTrue(fact.getKeySpec(pubKey, OpenSSHPublicKeySpec.class) instanceof OpenSSHPublicKeySpec);
+
         //
         // key without CRT coefficients
         //
@@ -637,6 +669,11 @@
             fail("private key hashCode check failed");
         }
 
+        if (!Arrays.areEqual(privKey.getEncoded(), crtKey.getEncoded()))
+        {
+            fail("encoding does not match");
+        }
+
         RSAPublicKey copyKey = (RSAPublicKey)keyFact.translateKey(pubKey);
         
         if (!pubKey.equals(copyKey))
@@ -713,6 +750,53 @@
         oaepDigestCheck("SHA3-256", NISTObjectIdentifiers.id_sha3_256, pub2048Key, priv2048Key, rand, Hex.decode("4460e68586ac0ca1832274919c6b9159d40ea1d383a32ca28f0e1a81962289c8c904fa117d90afd7bfefa51b6889d2d999efb72bafd4beb5594bb08f62532ecb077e4968f43e70673341e60649ed64ac49cf1a2396a64577767d8958217a251938a7ac1bbc1aec9c9197a2eb9a375c74a01097fe3717c8bde04f8a20df85ef10a59070970a4a6470131654cecb641d46e464f17ddfe7e0595025bf25f025edbd56b19487cfc87de1642ca5190f289cf78e2c4b1cf90e73ffae331581c23febcf490c32299f2e5bd5a354a0cc996cc692b5a318777d17e734b3c487ad615df7af0fc361af564e6970ee0aa9b140634cdcf1eda91d1a1156326caaa608c4d43ee4"));
         oaepDigestCheck("SHA3-384", NISTObjectIdentifiers.id_sha3_384, pub2048Key, priv2048Key, rand, Hex.decode("1b9f490569bddac8ea77e3bec8d6d38b159cb88545de86065dbc8757b35fdeb0cce90eaf93ec6d69d691c590fc3feec9974b80e0c0068929c77533be2066b4002ad6d195e473f72e8581255b8d2dfff016ff27ed50e6d3e63cfaf50851b2d43833eea8dab3b4506f517875659099815a96be6e8fd2dc1417c6e3ffadcfd3f494a5544267688d114d3eeaf43ea954686656afb7a3dc2f8a4cdc5d7b90a97acbbe32ff17b3d26d7eb2a4fbc847d49cc8cb8f837646613d1b5a78096cf3f48acf4be95205e0c4cb283447029eae1442fe3813a017604dfd59a9e841473f4d8914860f785fb2194b21cba47cd401bc32720f3df373e59336c3d64c61babd474b4bbe"));
         oaepDigestCheck("SHA3-512", NISTObjectIdentifiers.id_sha3_512, pub2048Key, priv2048Key, rand, Hex.decode("7b7870bb5ae52276a8b06b59f7321043afb1fa4e5dbca9f14bcce9efaacded531f090646ab0f8701b012cc93c51e0a8591043e6457cde1950f4ffc8ad87d946622ea48a70f95f40c22d88679eb92c10c19db487fd64857d723daf4ccfe749fdd05e6c0be28de57e09d3b5a0981322b6cc7a9743a50eec355a7af5bdcdcddc5e279ad90f599b68c47fdb39916c7a597cf989169e8667fd8602e88c9c128085d0e158ea75eeb37919a91cdf3f2cd5394adaadc4a2f25a6222d2637cb464841dc5820e54843495cb97af6b19edc72f137123813f5d78503232f79e4f617be3a9f09b0206634a2ecfe457dbd71d2d3d8e3dbca486e75e543f559dcea3112ad50a21d"));
+
+        testPSSKeys();
+    }
+
+    private void testPSSKeys()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSASSA-PSS", "BC");
+
+        isTrue("RSASSA-PSS".equals(kpGen.getAlgorithm()));
+        
+        KeyPair kp = kpGen.generateKeyPair();
+
+        isTrue("RSASSA-PSS".equals(kp.getPublic().getAlgorithm()));
+        isTrue("RSASSA-PSS".equals(kp.getPrivate().getAlgorithm()));
+
+        KeyFactory kFact = KeyFactory.getInstance("RSASSA-PSS", "BC");
+
+        isTrue("RSASSA-PSS".equals(kFact.getAlgorithm()));
+
+        PublicKey publicKey = kFact.generatePublic(new X509EncodedKeySpec(pssPublicKey));
+
+        isTrue("RSASSA-PSS".equals(publicKey.getAlgorithm()));
+
+        PrivateKey privateKey = kFact.generatePrivate(new PKCS8EncodedKeySpec(pssPrivateKey));
+
+        isTrue("RSASSA-PSS".equals(privateKey.getAlgorithm()));
+
+        publicKey = kFact.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded()));
+
+        isTrue("RSASSA-PSS".equals(publicKey.getAlgorithm()));
+
+        privateKey = kFact.generatePrivate(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
+
+        isTrue("RSASSA-PSS".equals(privateKey.getAlgorithm()));
+
+        privateKey = (PrivateKey)serializeDeserialize(kp.getPrivate());
+
+        isTrue(Arrays.areEqual(kp.getPrivate().getEncoded(), privateKey.getEncoded()));
+
+        SubjectPublicKeyInfo subKey = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
+
+        isTrue(null == subKey.getAlgorithm().getParameters());
+
+        PrivateKeyInfo privKey = PrivateKeyInfo.getInstance(privateKey.getEncoded());
+
+        isTrue(null == privKey.getPrivateKeyAlgorithm().getParameters());
     }
 
     public void oaepDigestCheck(String digest, ASN1ObjectIdentifier oid, PublicKey pubKey, PrivateKey privKey, SecureRandom rand, byte[] expected)
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RegressionTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RegressionTest.java
index fbc690b..896b9a5 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RegressionTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/RegressionTest.java
@@ -3,8 +3,8 @@
 import java.security.Security;
 
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
 
 public class RegressionTest
 {
@@ -87,27 +87,18 @@
         new GOST3410KeyPairTest(),
         new EdECTest(),
         new OpenSSHSpecTests(),
-        new SM2CipherTest()
+        new SM2CipherTest(),
+        new ZucTest(),
+        new ChaCha20Poly1305Test(),
+        new SipHash128Test()
     };
 
-    public static void main(
-        String[]    args)
+    public static void main(String[] args)
     {
         Security.addProvider(new BouncyCastleProvider());
 
         System.out.println("Testing " + Security.getProvider("BC").getInfo() + " version: " + Security.getProvider("BC").getVersion());
-        
-        for (int i = 0; i != tests.length; i++)
-        {
-            TestResult  result = tests[i].perform();
-            
-            if (result.getException() != null)
-            {
-                result.getException().printStackTrace();
-            }
-            
-            System.out.println(result);
-        }
+
+        SimpleTest.runTests(tests);
     }
 }
-
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SerialisationTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SerialisationTest.java
index 6d04f97..3bef852 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SerialisationTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SerialisationTest.java
@@ -1,9 +1,16 @@
 package org.bouncycastle.jce.provider.test;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.math.BigInteger;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.security.Security;
 import java.security.interfaces.DSAPrivateKey;
 import java.security.interfaces.DSAPublicKey;
 import java.security.interfaces.RSAPrivateCrtKey;
@@ -14,6 +21,7 @@
 
 import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
 import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.util.encoders.Base64;
 import org.bouncycastle.util.test.SimpleTest;
 
@@ -160,7 +168,7 @@
     }
 
     private void rsaTest()
-        throws IOException, ClassNotFoundException
+        throws Exception
     {
         RSAPublicKey pub = (RSAPublicKey)readObject(rsaPub);
 
@@ -173,6 +181,8 @@
             fail("public key exponent mismatch");
         }
 
+        isTrue(null != pub.getEncoded());
+
         RSAPublicKey pub2 = (RSAPublicKey)readObject(rsaPub2);
 
         if (!mod.equals(pub2.getModulus()))
@@ -184,6 +194,8 @@
             fail("public key 2 exponent mismatch");
         }
 
+        isTrue(null != pub.getEncoded());
+
         RSAPrivateCrtKey priv = (RSAPrivateCrtKey)readObject(rsaPriv);
 
         if (!mod.equals(priv.getModulus()))
@@ -214,10 +226,26 @@
         {
             fail("private key crt exponent mismatch");
         }
+
+        isTrue(null != priv.getEncoded());
+
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        testSerialisation(kp.getPublic());
+        testSerialisation(kp.getPrivate());
+
+        kpGen = KeyPairGenerator.getInstance("RSASSA-PSS", "BC");
+
+        kp = kpGen.generateKeyPair();
+
+        testSerialisation(kp.getPublic());
+        testSerialisation(kp.getPrivate());
     }
 
     private void elGamalTest()
-        throws IOException, ClassNotFoundException
+        throws Exception
     {
         ElGamalPublicKey pub = (ElGamalPublicKey)readObject(elGamalPub);
 
@@ -251,7 +279,7 @@
     }
 
     private void dhTest()
-        throws IOException, ClassNotFoundException
+        throws Exception
     {
         DHPublicKey pub = (DHPublicKey)readObject(dhPub);
 
@@ -290,10 +318,19 @@
         {
             fail("dh private key l mismatch");
         }
+
+        isTrue(null != priv.getEncoded());
+
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DH", "BC");
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        testDhSerialisation(kp.getPublic());
+        testDhSerialisation(kp.getPrivate());
     }
 
     private void dsaTest()
-        throws IOException, ClassNotFoundException
+        throws Exception
     {
         DSAPublicKey pub = (DSAPublicKey)readObject(dsaPub);
 
@@ -324,6 +361,15 @@
         {
             fail("dsa private key p mismatch");
         }
+
+        isTrue(null != priv.getEncoded());
+
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DSA", "BC");
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        testSerialisation(kp.getPublic());
+        testSerialisation(kp.getPrivate());
     }
 
     private Object readObject(byte[] key)
@@ -334,9 +380,40 @@
         return oIn.readObject();
     }
 
+    private void testDhSerialisation(Key key)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(key);
+
+        Key sKey = (Key)readObject(bOut.toByteArray());
+
+        // Diffie-Hellman keys drop the Q parameter on serialisation, perhaps this should change...
+        isTrue("alg mismatch: " + sKey.getAlgorithm(), key.getAlgorithm().equals(sKey.getAlgorithm()));
+//        isTrue("encoding mismatch: " + sKey.getAlgorithm() + " public: " + (sKey instanceof PublicKey), areEqual(key.getEncoded(), sKey.getEncoded()));
+    }
+
+    private void testSerialisation(Key key)
+        throws Exception
+    {
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(key);
+
+        Key sKey = (Key)readObject(bOut.toByteArray());
+
+        isTrue("alg mismatch: " + sKey.getAlgorithm(), key.getAlgorithm().equals(sKey.getAlgorithm()));
+        isTrue("encoding mismatch: " + sKey.getAlgorithm() + " public: " + (sKey instanceof PublicKey), areEqual(key.getEncoded(), sKey.getEncoded()));
+    }
+    
     public static void main(
         String[]    args)
     {
+        Security.addProvider(new BouncyCastleProvider());
+        
         runTest(new SerialisationTest());
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SimpleTestTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SimpleTestTest.java
new file mode 100644
index 0000000..1bd1eb4
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SimpleTestTest.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.Provider;
+import java.security.Security;
+
+import junit.framework.TestCase;
+import org.bouncycastle.util.test.SimpleTestResult;
+
+public class SimpleTestTest
+    extends TestCase
+{
+    public void testJCE()
+    {
+        org.bouncycastle.util.test.Test[] tests = RegressionTest.tests;
+
+        for (int i = 0; i != tests.length; i++)
+        {
+            SimpleTestResult result = (SimpleTestResult)tests[i].perform();
+
+            if (!result.isSuccessful())
+            {
+                if (result.getException() != null)
+                {
+                    result.getException().printStackTrace();
+                }
+                fail("index " + i + " " + result.toString());
+            }
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SipHash128Test.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SipHash128Test.java
new file mode 100644
index 0000000..9183ef9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/SipHash128Test.java
@@ -0,0 +1,129 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+
+public class SipHash128Test
+    extends SimpleTest
+{
+    public void performTest()
+        throws Exception
+    {
+        testMac();
+        testKeyGenerator();
+    }
+
+    private void testKeyGenerator()
+        throws NoSuchAlgorithmException,
+        NoSuchProviderException
+    {
+        testKeyGen("SipHash128");
+        testKeyGen("SipHash128-2-4");
+        testKeyGen("SipHash128-4-8");
+    }
+
+    private void testKeyGen(String algorithm)
+        throws NoSuchAlgorithmException,
+        NoSuchProviderException
+    {
+        KeyGenerator kg = KeyGenerator.getInstance(algorithm, "BC");
+
+        SecretKey key = kg.generateKey();
+
+        if (!key.getAlgorithm().equalsIgnoreCase("SipHash128"))
+        {
+            fail("Unexpected algorithm name in key", "SipHash128", key.getAlgorithm());
+        }
+        if (key.getEncoded().length != 16)
+        {
+            fail("Expected 128 bit key");
+        }
+    }
+
+    private void testMac()
+        throws NoSuchAlgorithmException,
+        NoSuchProviderException,
+        InvalidKeyException
+    {
+        byte[] key = Hex.decode("000102030405060708090a0b0c0d0e0f");
+        byte[] input = Hex.decode("000102030405060708090a0b0c0d0e");
+
+        byte[] expected = Hex.decode("5493e99933b0a8117e08ec0f97cfc3d9");
+
+        Mac mac = Mac.getInstance("SipHash128", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash128"));
+
+        mac.update(input, 0, input.length);
+
+        byte[] result = mac.doFinal();
+
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for doFinal()");
+        }
+
+        mac.init(new SecretKeySpec(key, "SipHash128-2-4"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for second doFinal()");
+        }
+
+        mac = Mac.getInstance("SipHash128-2-4", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash128-2-4"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for alias");
+        }
+
+        // SipHash128 4-8
+        expected = Hex.decode("284d03303a453a593d78f7fadc9062cb");
+
+        mac = Mac.getInstance("SipHash128-4-8", "BC");
+
+        mac.init(new SecretKeySpec(key, "SipHash128"));
+
+        mac.update(input, 0, input.length);
+
+        result = mac.doFinal();
+
+        if (!Arrays.areEqual(expected, result))
+        {
+            fail("Result does not match expected value for SipHash128 4-8");
+        }
+    }
+
+    public String getName()
+    {
+        return "SipHash128";
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        runTest(new SipHash128Test());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/TestUtils.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/TestUtils.java
index 1bc8d0a..43f3165 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/TestUtils.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/TestUtils.java
@@ -32,6 +32,7 @@
 import org.bouncycastle.asn1.DERNull;
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
+import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -52,7 +53,6 @@
 import org.bouncycastle.asn1.x509.Time;
 import org.bouncycastle.asn1.x509.V1TBSCertificateGenerator;
 import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
-import org.bouncycastle.asn1.x509.X509Extensions;
 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.digests.SHA1Digest;
@@ -75,6 +75,7 @@
         algIds.put("SHA256withRSA", new AlgorithmIdentifier(PKCSObjectIdentifiers.sha256WithRSAEncryption, DERNull.INSTANCE));
         algIds.put("SHA1withECDSA", new AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA1));
         algIds.put("SHA256withECDSA", new AlgorithmIdentifier(X9ObjectIdentifiers.ecdsa_with_SHA256));
+        algIds.put("Ed448", new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed448));
     }
 
     public static X509Certificate createSelfSignedCert(String dn, String sigName, KeyPair keyPair)
@@ -137,21 +138,19 @@
         certGen.setSubjectPublicKeyInfo(SubjectPublicKeyInfo.getInstance(pubKey.getEncoded()));
         certGen.setExtensions(extensions);
 
-        Signature sig = Signature.getInstance(sigName, "BC");
-
-        sig.initSign(signerKey);
-
-        sig.update(certGen.generateTBSCertificate().getEncoded(ASN1Encoding.DER));
-
         TBSCertificate tbsCert = certGen.generateTBSCertificate();
 
-        ASN1EncodableVector v = new ASN1EncodableVector();
+        Signature sig = Signature.getInstance(sigName, "BC");
+        sig.initSign(signerKey);
+        sig.update(tbsCert.getEncoded(ASN1Encoding.DER));
 
+        ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(tbsCert);
         v.add((AlgorithmIdentifier)algIds.get(sigName));
         v.add(new DERBitString(sig.sign()));
 
-        return (X509Certificate)CertificateFactory.getInstance("X.509", "BC").generateCertificate(new ByteArrayInputStream(new DERSequence(v).getEncoded(ASN1Encoding.DER)));
+        return (X509Certificate)CertificateFactory.getInstance("X.509", "BC")
+            .generateCertificate(new ByteArrayInputStream(new DERSequence(v).getEncoded(ASN1Encoding.DER)));
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ZucTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ZucTest.java
new file mode 100644
index 0000000..c4b3890
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/ZucTest.java
@@ -0,0 +1,296 @@
+package org.bouncycastle.jce.provider.test;
+
+import java.security.AlgorithmParameters;
+import java.security.Security;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+import org.bouncycastle.util.encoders.Hex;
+import org.bouncycastle.util.test.SimpleTest;
+import org.bouncycastle.util.test.Test;
+import org.bouncycastle.util.test.TestResult;
+
+public class ZucTest
+    extends SimpleTest
+{
+    private static final String KEY128_1 =
+        "00000000000000000000000000000000";
+    private static final String KEY128_2 =
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+    private static final String KEY256_1 =
+        "00000000000000000000000000000000" +
+            "00000000000000000000000000000000";
+    private static final String KEY256_2 =
+        "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +
+            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+
+    private static final String IV128_1 = "00000000000000000000000000000000";
+    private static final String IV128_2 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
+    private static final String IV200_1 = "00000000000000000000000000000000000000000000000000";
+    private static final String IV200_2 = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F3F3F3F3F3F3F";
+
+    private final TestCase ZUC128_TEST1 = new TestCase(KEY128_1, IV128_1,
+        "27bede74018082da87d4e5b69f18bf6632070e0f39b7b692b4673edc3184a48e27636f4414510d62cc15cfe194ec4f6d4b8c8fcc630648badf41b6f9d16a36ca"
+    );
+    private final TestCase ZUC128_TEST2 = new TestCase(KEY128_2, IV128_2,
+        "0657cfa07096398b734b6cb4883eedf4257a76eb97595208d884adcdb1cbffb8e0f9d15846a0eed015328503351138f740d079af17296c232c4f022d6e4acac6"
+    );
+    private final TestCase ZUC256_TEST1 = new TestCase(KEY256_1, IV200_1,
+        "58d03ad62e032ce2dafc683a39bdcb0352a2bc67f1b7de74163ce3a101ef55589639d75b95fa681b7f090df756391ccc903b7612744d544c17bc3fad8b163b08"
+    );
+    private final TestCase ZUC256_TEST2 = new TestCase(KEY256_2, IV200_2,
+        "3356cbaed1a1c18b6baa4ffe343f777c9e15128f251ab65b949f7b26ef7157f296dd2fa9df95e3ee7a5be02ec32ba585505af316c2f9ded27cdbd935e441ce11"
+    );
+
+    private final TestCase MAC128_TEST1 = new TestCase(KEY128_1, IV128_1, "508dd5ff");
+    private final TestCase MAC128_TEST2 = new TestCase(KEY128_1, IV128_1, "fbed4c12");
+    private final TestCase MAC256_TEST1 = new TestCase(KEY256_1, IV200_1, "d85e54bbcb9600967084c952a1654b26");
+    private final TestCase MAC256_TEST2 = new TestCase(KEY256_1, IV200_1, "df1e8307b31cc62beca1ac6f8190c22f");
+    private final TestCase MAC256_64_TEST1 = new TestCase(KEY256_1, IV200_1, "673e54990034d38c");
+    private final TestCase MAC256_64_TEST2 = new TestCase(KEY256_1, IV200_1, "130dc225e72240cc");
+    private final TestCase MAC256_32_TEST1 = new TestCase(KEY256_1, IV200_1, "9b972a74");
+    private final TestCase MAC256_32_TEST2 = new TestCase(KEY256_1, IV200_1, "8754f5cf");
+    
+    public String getName()
+    {
+        return "Zuc";
+    }
+
+    /**
+     * Test the Cipher against the results.
+     *
+     * @param pCipher   the cipher to test.
+     * @param pTestCase the testCase
+     */
+    void testCipher(final Cipher pCipher,
+                    final TestCase pTestCase)
+    throws Exception
+    {
+        /* Access the expected bytes */
+        final byte[] myExpected = Hex.decode(pTestCase.theExpected);
+
+        /* Create the output buffer */
+        final byte[] myOutput = new byte[myExpected.length];
+
+        /* Access plainText or nulls */
+        final byte[] myData = pTestCase.thePlainText != null
+            ? Hex.decode(pTestCase.thePlainText)
+            : new byte[myExpected.length];
+
+        /* Access the key and the iv */
+        final SecretKey myKey = new SecretKeySpec(Hex.decode(pTestCase.theKey), pCipher.getAlgorithm());
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+
+        /* Initialise the cipher and create the keyStream */
+        pCipher.init(Cipher.ENCRYPT_MODE, myKey, new IvParameterSpec(myIV));
+
+        pCipher.doFinal(myData, 0, myData.length, myOutput, 0);
+
+        /* Check the encryption */
+        isTrue("Encryption mismatch", Arrays.areEqual(myExpected, myOutput));
+
+        AlgorithmParameters algParams = AlgorithmParameters.getInstance(pCipher.getAlgorithm(), "BC");
+
+        algParams.init(new IvParameterSpec(myIV));
+
+        pCipher.init(Cipher.DECRYPT_MODE, myKey, algParams);
+
+        pCipher.doFinal(myData, 0, myData.length, myOutput, 0);
+    }
+
+    /**
+     * Test the Mac against the results.
+     *
+     * @param pMac      the mac to test.
+     * @param pOnes     use all ones as data?
+     * @param pTestCase the testCase
+     */
+    void testMac(final Mac pMac,
+                 final boolean pOnes,
+                 final TestCase pTestCase)
+        throws Exception
+    {
+        /* Access the expected bytes */
+        final byte[] myExpected = Hex.decode(pTestCase.theExpected);
+
+        /* Create the output buffer and the data */
+        final byte[] myOutput = new byte[pMac.getMacLength()];
+
+        isTrue("Mac length mismatch", myExpected.length == myOutput.length);
+
+        final byte[] myData = new byte[(pOnes ? 4000 : 400) / 8];
+        Arrays.fill(myData, (byte)(pOnes ? 0x11 : 0));
+
+        /* Access the key and the iv */
+        final SecretKey myKey = new SecretKeySpec(Hex.decode(pTestCase.theKey), pMac.getAlgorithm());
+        final byte[] myIV = Hex.decode(pTestCase.theIV);
+
+        /* Initialise the cipher and create the keyStream */
+        pMac.init(myKey, new IvParameterSpec(myIV));
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        /* Check the mac */
+        isTrue("Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+
+        /* Check doFinal reset */
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        isTrue("DoFinal Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+
+        /* Check reset() */
+        pMac.update(myData, 0, myData.length);
+
+        pMac.reset();
+
+        pMac.update(myData, 0, myData.length);
+        pMac.doFinal(myOutput, 0);
+
+        isTrue("Reset Mac mismatch", Arrays.areEqual(myExpected, myOutput));
+    }
+
+    private void simpleTest(Cipher zuc)
+        throws Exception
+    {
+        KeyGenerator kGen = KeyGenerator.getInstance(zuc.getAlgorithm(), "BC");
+        byte[] msg = Strings.toByteArray("Hello, world!");
+        SecretKey k = kGen.generateKey();
+
+        zuc.init(Cipher.ENCRYPT_MODE, k);
+
+        byte[] enc = zuc.doFinal(msg);
+
+        byte[] iv = zuc.getIV();
+        AlgorithmParameters algParam = zuc.getParameters();
+
+        zuc.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(iv));
+
+        byte[] dec = zuc.doFinal(enc);
+
+        areEqual(msg, dec);
+
+        zuc.init(Cipher.DECRYPT_MODE, k, algParam);
+
+        dec = zuc.doFinal(enc);
+
+        areEqual(msg, dec);
+    }
+
+    public void performTest()
+        throws Exception
+    {
+        final Cipher zuc128 = Cipher.getInstance("Zuc-128", "BC");
+        testCipher(zuc128, ZUC128_TEST1);
+        testCipher(zuc128, ZUC128_TEST2);
+
+        simpleTest(zuc128);
+
+        final Cipher zuc256 = Cipher.getInstance("Zuc-256", "BC");
+        testCipher(zuc256, ZUC256_TEST1);
+        testCipher(zuc256, ZUC256_TEST2);
+
+        simpleTest(zuc256);
+
+        final Mac mac128 = Mac.getInstance("Zuc-128", "BC");
+
+        // check reset
+        mac128.reset();
+
+        testMac(mac128, false, MAC128_TEST1);
+        testMac(mac128, true, MAC128_TEST2);
+
+        final Mac mac256 = Mac.getInstance("Zuc-256", "BC");
+
+        // check reset
+        mac256.reset();
+        
+        testMac(mac256, false, MAC256_TEST1);
+        testMac(mac256, true, MAC256_TEST2);
+
+        final Mac mac256_128 = Mac.getInstance("Zuc-256-128", "BC");
+
+        testMac(mac256_128, false, MAC256_TEST1);
+        testMac(mac256_128, true, MAC256_TEST2);
+
+        final Mac mac256_64 = Mac.getInstance("Zuc-256-64", "BC");
+
+        testMac(mac256_64, false, MAC256_64_TEST1);
+        testMac(mac256_64, true, MAC256_64_TEST2);
+
+        final Mac mac256_32 = Mac.getInstance("Zuc-256-32", "BC");
+
+        testMac(mac256_32, false, MAC256_32_TEST1);
+        testMac(mac256_32, true, MAC256_32_TEST2);
+    }
+
+    /**
+     * The TestCase.
+     */
+    private static class TestCase
+    {
+        /**
+         * The testCase.
+         */
+        private final String theKey;
+        private final String theIV;
+        private final String thePlainText;
+        private final String theExpected;
+
+        /**
+         * Constructor.
+         *
+         * @param pKey      the key
+         * @param pIV       the IV
+         * @param pExpected the expected results.
+         */
+        TestCase(final String pKey,
+                 final String pIV,
+                 final String pExpected)
+        {
+            this(pKey, pIV, null, pExpected);
+        }
+
+        /**
+         * Constructor.
+         *
+         * @param pKey      the key
+         * @param pIV       the IV
+         * @param pPlain    the plainText
+         * @param pExpected the expected results.
+         */
+        TestCase(final String pKey,
+                 final String pIV,
+                 final String pPlain,
+                 final String pExpected)
+        {
+            theKey = pKey;
+            theIV = pIV;
+            thePlainText = pPlain;
+            theExpected = pExpected;
+        }
+    }
+
+    public static void main(
+        String[]    args)
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        Test test = new ZucTest();
+        TestResult result = test.perform();
+
+        System.out.println(result.toString());
+        if (result.getException() != null)
+        {
+            result.getException().printStackTrace();
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/AllTests.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/AllTests.java
index 434631a..a193dd0 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/AllTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/AllTests.java
@@ -21,6 +21,7 @@
         TestSuite suite = new TestSuite("CertPath Tests");
         
         suite.addTestSuite(NistCertPathTest.class);
+        suite.addTestSuite(NistCertPathTest2.class);
         suite.addTestSuite(NistCertPathReviewerTest.class);
 
         return new BCTestSetup(suite);
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
index 1ee8ded..a98fed7 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathReviewerTest.java
@@ -520,7 +520,7 @@
         {
             ErrorBundle msg = (ErrorBundle) result.getErrors(index).iterator().next();
             assertEquals(messageId,msg.getId());
-            assertEquals(message,msg.getText(Locale.ENGLISH,TimeZone.getTimeZone("GMT")));
+            assertEquals(message,msg.getText(Locale.ENGLISH,TimeZone.getTimeZone("GMT")).replace("Greenwich Mean Time", "GMT"));
         }
     }
     
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
index af94e4e..b91b46b 100644
--- a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest.java
@@ -802,7 +802,7 @@
         String crlName)
         throws Exception
     {
-        X509CRL crl = (X509CRL)certs.get(crlName);
+        X509CRL crl = (X509CRL)crls.get(crlName);
         
         if (crl != null)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest2.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest2.java
new file mode 100644
index 0000000..683a4b6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/NistCertPathTest2.java
@@ -0,0 +1,4575 @@
+package org.bouncycastle.jce.provider.test.nist;
+
+import java.security.Security;
+
+import junit.framework.TestCase;
+
+// tests based on https://csrc.nist.gov/CSRC/media/Projects/PKI-Testing/documents/PKITS.pdf
+//
+public class NistCertPathTest2
+    extends TestCase
+{
+
+    public void setUp()
+    {
+        if (Security.getProvider("BC") == null)
+        {
+            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+        }
+    }
+
+    /**
+     * 4.1.1 Valid Signatures Test1
+     * <p>
+     * The purpose of this test is to verify an application's ability to name chain, signature chain, and
+     * check validity dates, on certificates in a certification path. It also tests processing of the basic
+     * constraints and key usage extensions in intermediate certificates.
+     */
+    public void test4_1_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Certificate Path Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.1.2 Invalid CA Signature Test2
+     * <p>
+     * The purpose of this test is to verify an application's ability to recognize an invalid signature on an
+     * intermediate certificate in a certification path.
+     */
+    public void test4_1_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid CA Signature Test2 EE")
+            .withCrls("Bad Signed CA CRL")
+            .withCACert("Bad Signed CA Cert")
+            .doExceptionTest(1, "TrustAnchor found but certificate validation failed.");
+    }
+
+    /**
+     * 4.1.3 Invalid EE Signature Test3
+     * <p>
+     * The purpose of this test is to verify an application's ability to recognize an invalid signature on an
+     * end entity certificate in a certification path.
+     */
+    public void test4_1_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid EE Signature Test3 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "Could not validate certificate signature.");
+    }
+
+    /**
+     * 4.1.4 Valid DSA Signatures Test4
+     * <p>
+     * The purpose of this test is to verify an application's ability to validate certificate in which DSA
+     * signatures are used. The intermediate CA and the end entity have DSA key pairs.
+     */
+    public void test4_1_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DSA Signatures Test4 EE")
+            .withCrls("DSA CA CRL")
+            .withCACert("DSA CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.1.5 Valid DSA Parameter Inheritance Test5
+     * <p>
+     * The purpose of this test is to verify an application's ability to validate DSA signatures when the
+     * DSA parameters are not included in a certificate and need to be inherited from a previous
+     * certificate in the path. The intermediate CAs and the end entity have DSA key pairs.
+     */
+    public void test4_1_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DSA Parameter Inheritance Test5 EE")
+            .withCrls("DSA Parameters Inherited CA CRL")
+            .withCACert("DSA Parameters Inherited CA Cert")
+            .withCrls("DSA CA CRL")
+            .withCACert("DSA CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.1.6 Invalid DSA Signature Test6
+     * <p>
+     * The purpose of this test is to verify an application's ability to determine when a DSA signature is
+     * invalid. The intermediate CA and the end entity have DSA key pairs.
+     */
+    public void test4_1_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DSA Signature Test6 EE")
+            .withCrls("DSA CA CRL")
+            .withCACert("DSA CA Cert")
+            .doExceptionTest(0, "Could not validate certificate signature.");
+    }
+
+    /**
+     * 4.2.1 Invalid CA notBefore Date Test1
+     * <p>
+     * In this test, the intermediate certificate's notBefore date is after the current date.
+     */
+    public void test4_2_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid CA notBefore Date Test1 EE")
+            .withCrls("Bad notBefore Date CA CRL")
+            .withCACert("Bad notBefore Date CA Cert")
+            .doExceptionTest(1, "Could not validate certificate: certificate not valid till 20470101120100GMT+00:00");
+    }
+
+    /**
+     * 4.2.2 Invalid EE notBefore Date Test2
+     * <p>
+     * In this test, the end entity certificate's notBefore date is after the current date.
+     */
+    public void test4_2_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid EE notBefore Date Test2 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "Could not validate certificate: certificate not valid till 20470101120100GMT+00:00");
+
+    }
+
+    /**
+     * 4.2.3 Valid pre2000 UTC notBefore Date Test3
+     * <p>
+     * In this test, the end entity certificate's notBefore date is set to 1950 and is encoded in UTCTime.
+     */
+    public void test4_2_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid pre2000 UTC notBefore Date Test3 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.2.4 Valid GeneralizedTime notBefore Date Test4
+     * <p>
+     * In this test, the end entity certificate's notBefore date is specified in GeneralizedTime.
+     */
+    public void test4_2_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid GeneralizedTime notBefore Date Test4 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.2.5 Invalid CA notAfter Date Test5
+     * <p>
+     * In this test, the intermediate certificate's notAfter date is before the current date.
+     * 9
+     */
+    public void test4_2_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid CA notAfter Date Test5 EE")
+            .withCrls("Bad notAfter Date CA CRL")
+            .withCACert("Bad notAfter Date CA Cert")
+            .doExceptionTest(1, "Could not validate certificate: certificate expired on 20020101120100GMT+00:00");
+    }
+
+    /**
+     * 4.2.6 Invalid EE notAfter Date Test6
+     * <p>
+     * In this test, the end entity certificate's notAfter date is before the current date.
+     */
+    public void test4_2_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid EE notAfter Date Test6 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "Could not validate certificate: certificate expired on 20020101120100GMT+00:00");
+    }
+
+    /**
+     * 4.2.7 Invalid pre2000 UTC EE notAfter Date Test7
+     * <p>
+     * In this test, the end entity certificate's notAfter date is 1999 and is encoded in UTCTime.
+     */
+    public void test4_2_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pre2000 UTC EE notAfter Date Test7 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "Could not validate certificate: certificate expired on 19990101120100GMT+00:00");
+    }
+
+    /**
+     * 4.2.8 Valid GeneralizedTime notAfter Date Test8
+     * <p>
+     * In this test, the end entity certificate's notAfter date is 2050 and is encoded in GeneralizedTime.
+     */
+    public void test4_2_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid GeneralizedTime notAfter Date Test8 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.1 Invalid Name Chaining EE Test1
+     * <p>
+     * In this test, the common name (cn=) portion of the issuer's name in the end entity certificate does
+     * not match the common name portion of the subject's name in the preceding intermediate certificate.
+     */
+    public void test4_3_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Name Chaining Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=Good CA Root,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.3.2 Invalid Name Chaining Order Test2
+     * <p>
+     * In this test, the issuer's name in the end entity certificate and the subject's name in the preceding
+     * intermediate certificate contain the same relative distinguished names (RDNs), but their ordering is
+     * different.
+     */
+    public void test4_3_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Name Chaining Order Test2 EE")
+            .withCrls("Name Order CA CRL")
+            .withCACert("Name Ordering CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=Name Ordering CA,ou=Organizational Unit Name 1,ou=Organizational Unit Name 2,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.3.3 Valid Name Chaining Whitespace Test3
+     * <p>
+     * In this test, the issuer's name in the end entity certificate and the subject's name in the preceding
+     * intermediate certificate differ in internal whitespace, but match once the internal whitespace is
+     * compressed.
+     */
+    public void test4_3_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Name Chaining Whitespace Test3 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.4 Valid Name Chaining Whitespace Test4
+     * <p>
+     * In this test, the issuer's name in the end entity certificate and the subject's name in the preceding
+     * intermediate certificate differ in leading and trailing whitespace, but match once all leading and
+     * trailing whitespace is removed.
+     */
+    public void test4_3_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Name Chaining Whitespace Test4 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.5 Valid Name Chaining Capitalization Test5
+     * <p>
+     * In this test, the issuer's name in the end entity certificate and the subject's name in the preceding
+     * intermediate certificate differ in capitalization, but match when a case insensitive match is
+     * performed.
+     */
+    public void test4_3_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Name Chaining Capitalization Test5 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.6 Valid Name Chaining UIDs Test6
+     * <p>
+     * In this test, the intermediate certificate includes a subjectUniqueID and the end entity certificate
+     * includes a matching issuerUniqueID.
+     * 12
+     */
+    public void test4_3_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Name UIDs Test6 EE")
+            .withCrls("UID CA CRL")
+            .withCACert("UID CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.7 Valid RFC3280 Mandatory Attribute Types Test7
+     * <p>
+     * In this test, this intermediate certificate includes a subject name that includes the attribute types
+     * distinguished name qualifier, state or province name, serial number, domain component,
+     * organization, and country.
+     */
+    public void test4_3_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid RFC3280 Mandatory Attribute Types Test7 EE")
+            .withCrls("RFC3280 Mandatory AttributeTypes CA CRL")
+            .withCACert("RFC3280 Mandatory Attribute Types CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.8 Valid RFC3280 Optional Attribute Types Test8
+     * <p>
+     * In this test, this intermediate certificate includes a subject name that includes the attribute types
+     * locality, title, surname, given name, initials, pseudonym, generation qualifier, organization, and
+     * country.
+     */
+    public void test4_3_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid RFC3280 Optional Attribute Types Test8 EE")
+            .withCrls("RFC3280 Optional AttributeTypes CA CRL")
+            .withCACert("RFC3280 Optional Attribute Types CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.9 Valid UTF8String Encoded Names Test9
+     * <p>
+     * In this test, the attribute values for the common name and organization attribute types in the
+     * subject fields of the intermediate and end certificates and the issuer fields of the end certificate
+     * and the intermediate certificate's CRL are encoded in UTF8String.
+     * 13
+     */
+    public void test4_3_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid UTF8String Encoded Names Test9 EE")
+            .withCrls("UTF8String Encoded Names CA CRL")
+            .withCACert("UTF8String Encoded Names CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.10 Valid Rollover from PrintableString to UTF8String Test10
+     * <p>
+     * In this test, the attribute values for the common name and organization attribute types in the issuer
+     * and subject fields of the end certificate and the issuer field of the intermediate certificate's CRL
+     * are encoded in UTF8String. However, these attribute types are encoded in PrintableString in the
+     * subject field of the intermediate certificate.
+     */
+    public void test4_3_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Rollover from PrintableString to UTF8String Test10 EE")
+            .withCrls("Rollover fromPrintableString to UTF8String CA CRL")
+            .withCACert("Rollover from PrintableString to UTF8String CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.3.11 Valid UTF8String Case Insensitive Match Test11
+     * <p>
+     * In this test, the attribute values for the common name and organization attribute types in the
+     * subject fields of the intermediate and end certificates and the issuer fields of the end certificate
+     * and the intermediate certificate's CRL are encoded in UTF8String. The subject of the
+     * intermediate certificate and the issuer of the end certificate differ in capitalization and whitespace,
+     * but match when a case insensitive match is performed.
+     */
+    public void test4_3_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid UTF8String Case Insensitive Match Test11 EE")
+            .withCrls("UTF8String Case InsensitiveMatch CA CRL")
+            .withCACert("UTF8String Case Insensitive Match CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.1 Missing CRL Test1
+     * <p>
+     * In this test, there is no revocation information available from the intermediate CA, making it
+     * impossible to determine the status of the end certificate.
+     */
+    public void test4_4_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Missing CRL Test1 EE")
+            .withCACert("No CRL CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=No CRL CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.4.2 Invalid Revoked CA Test2
+     * <p>
+     * In this test, the CRL issued by the first intermediate CA indicates that the second intermediate
+     * certificate in the path has been revoked.
+     */
+    public void test4_4_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Revoked CA Test2 EE")
+            .withCrls("Revoked subCA CRL")
+            .withCACert("Revoked subCA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(1, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.4.3 Invalid Revoked EE Test3
+     * <p>
+     * In this test, the CRL issued by the intermediate CA indicates that the end entity certificate has been
+     * revoked.
+     */
+    public void test4_4_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Revoked EE Test3 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.4.4 Invalid Bad CRL Signature Test4
+     * <p>
+     * In this test, the signature on the CRL issued by the intermediate CA is invalid.
+     */
+    public void test4_4_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Bad CRL Signature Test4 EE")
+            .withCrls("Bad CRL Signature CA CRL")
+            .withCACert("Bad CRL Signature CA Cert")
+            .doExceptionTest(0, "Cannot verify CRL.");
+    }
+
+    /**
+     * 4.4.5 Invalid Bad CRL Issuer Name Test5
+     * <p>
+     * In this test, the issuer name in the CRL signed by the intermediate CA does not match the issuer
+     * name in the end entity's certificate.
+     */
+    public void test4_4_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Bad CRL Issuer Name Test5 EE")
+            .withCrls("Bad CRL Issuer Name CA CRL")
+            .withCACert("Bad CRL Issuer Name CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=Bad CRL Issuer Name CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.4.6 Invalid Wrong CRL Test6
+     * <p>
+     * In this test, the wrong CRL is in the intermediate certificate's directory entry. There is no CRL
+     * available from the intermediate CA making it impossible to determine the status of the end entity's
+     * certificate.
+     */
+    public void test4_4_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Wrong CRL Test6 EE")
+            .withCrls("Wrong CRL CA CRL")
+            .withCACert("Wrong CRL CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=Wrong CRL CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.4.7 Valid Two CRLs Test7
+     * <p>
+     * In this test, there are two CRLs in the intermediate CAs directory entry, one that is correct and one
+     * that contains the wrong issuer name. The correct CRL does not list any certificates as revoked.
+     * The incorrect CRL includes the serial number of the end entity's certificate on its list of revoked
+     * certificates.
+     */
+    public void test4_4_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Two CRLs Test7 EE")
+            .withCrls("Two CRLs CA Bad CRL")
+            .withCrls("Two CRLs CA Good CRL")
+            .withCACert("Two CRLs CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.8 Invalid Unknown CRL Entry Extension Test8
+     * <p>
+     * In this test, the end entity's certificate has been revoked. In the intermediate CA's CRL, there is a
+     * made up critical crlEntryExtension associated with the end entity certificate's serial number.
+     * [X.509 7.3] When an implementation processing a CRL encounters the serial number of the
+     * certificate of interest in a CRL entry, but does not recognize a critical extension in the
+     * crlEntryExtensions field from that CRL entry, that CRL cannot be used to determine the status of
+     * the certificate.
+     */
+    public void test4_4_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Unknown CRL Entry Extension Test8 EE")
+            .withCrls("Unknown CRL Entry Extension CACRL")
+            .withCACert("Unknown CRL Entry Extension CA Cert")
+            .doExceptionTest(0, "CRL entry has unsupported critical extensions.");
+    }
+
+    /**
+     * 4.4.9 Invalid Unknown CRL Extension Test9
+     * <p>
+     * In this test, the end entity's certificate has been revoked. In the intermediate CA's CRL, there is a
+     * made up critical extension in the crlExtensions field.
+     * [X.509 7.3] When an implementation does not recognize a critical extension in the crlExtensions
+     * field, that CRL cannot be used to determine the status of the certificate, regardless of whether the
+     * serial number of the certificate of interest appears in that CRL or not.
+     */
+    public void test4_4_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Unknown CRL Extension Test9 EE")
+            .withCrls("Unknown CRL Extension CA CRL")
+            .withCACert("Unknown CRL Extension CA Cert")
+            .doExceptionTest(0, "CRL contains unsupported critical extensions.");
+    }
+
+    /**
+     * 4.4.10 Invalid Unknown CRL Extension Test10
+     * <p>
+     * In this test the intermediate CA's CRL contains a made up critical extension in the crlExtensions
+     * field. The end entity certificate's serial number is not listed on the CRL, however, due to the
+     * presence of an unknown critical CRL extension, the relying party can not be sure that the list of
+     * serial numbers on the revokedCertificates list includes all certificates that have been revoked by
+     * the intermediate CA. As a result, the relying party can not verify that the end entity's certificate
+     * has not been revoked.
+     * 18
+     */
+    public void test4_4_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Unknown CRL Extension Test10 EE")
+            .withCrls("Unknown CRL Extension CA CRL")
+            .withCACert("Unknown CRL Extension CA Cert")
+            .doExceptionTest(0, "CRL contains unsupported critical extensions.");
+    }
+
+    /**
+     * 4.4.11 Invalid Old CRL nextUpdate Test11
+     * <p>
+     * In this test the intermediate CA's CRL has a nextUpdate time that is far in the past (January
+     * 2010), indicating that the CA has already issued updated revocation information. Since the
+     * information in the CRL is out-of-date and a more up-to-date CRL (that should have already been
+     * issued) can not be obtained, the certification path should be treated as if the status of the end entity
+     * certificate can not be determined.3
+     */
+    public void test4_4_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Old CRL nextUpdate Test11 EE")
+            .withCrls("Old CRL nextUpdate CA CRL")
+            .withCACert("Old CRL nextUpdate CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=Old CRL nextUpdate CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.4.12 Invalid pre2000 CRL nextUpdate Test12
+     * <p>
+     * In this test the intermediate CA's CRL has a nextUpdate time that is in 1999 indicating that the
+     * CA has already issued updated revocation information. Since the information in the CRL is outof-date and a more up-to-date CRL (that should have already been issued) can not be obtained, the
+     * certification path should be treated as if the status of the end entity certificate can not be
+     * determined.
+     */
+    public void test4_4_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pre2000 CRL nextUpdate Test12 EE")
+            .withCrls("pre2000 CRL nextUpdate CA CRL")
+            .withCACert("pre2000 CRL nextUpdate CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=pre2000 CRL nextUpdate CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.4.13 Valid GeneralizedTime CRL nextUpdate Test13
+     * <p>
+     * In this test the intermediate CA's CRL has a nextUpdate time that is in 2050. Since the
+     * nextUpdate time is in the future, this CRL may contain the most up-to-date certificate status
+     * information that is available from the intermediate CA and so the relying party may use this CRL
+     * to determine the status of the end entity certificate.
+     */
+    public void test4_4_13()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid GeneralizedTime CRL nextUpdate Test13 EE")
+            .withCrls("GeneralizedTime CRL nextUpdateCA CRL")
+            .withCACert("GeneralizedTime CRL nextUpdate CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.14 Valid Negative Serial Number Test14
+     * <p>
+     * RFC 3280 mandates that certificate serial numbers be positive integers, but states that relying
+     * parties should be prepared to gracefully handle certificates with serial numbers that are negative,
+     * or zero. In this test, the end entity's certificate has a serial number of 255 (DER encoded as "00
+     * FF") and the corresponding CRL lists the certificate with serial number -1 (DER encoded as "FF")
+     * as revoked.
+     */
+    public void test4_4_14()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Negative Serial Number Test14 EE")
+            .withCrls("Negative Serial Number CA CRL")
+            .withCACert("Negative Serial Number CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.15 Invalid Negative Serial Number Test15
+     * <p>
+     * RFC 3280 mandates that certificate serial numbers be positive integers, but states that relying
+     * parties should be prepared to gracefully handle certificates with serial numbers that are negative,
+     * or zero. In this test, the end entity's certificate has a serial number of -1 (DER encoded as "FF")
+     * and the corresponding CRL lists this certificate as revoked.
+     */
+    public void test4_4_15()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Negative Serial Number Test15 EE")
+            .withCrls("Negative Serial Number CA CRL")
+            .withCACert("Negative Serial Number CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.4.16 Valid Long Serial Number Test16
+     * <p>
+     * RFC 3280 mandates that certificate users be able to handle serial number values up to 20 octets
+     * long. In this test, the end entity's certificate has a 20 octet serial number that is not listed on the
+     * corresponding CRL, but the serial number matches the serial number listed on the CRL in all but
+     * the least significant octet.
+     */
+    public void test4_4_16()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Long Serial Number Test16 EE")
+            .withCrls("Long Serial Number CA CRL")
+            .withCACert("Long Serial Number CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.17 Valid Long Serial Number Test17
+     * <p>
+     * RFC 3280 mandates that certificate users be able to handle serial number values up to 20 octets
+     * long. In this test, the end entity's certificate has a 20 octet serial number that is not listed on the
+     * corresponding CRL, but the serial number matches the serial number listed on the CRL in all but
+     * the most significant octet.
+     */
+    public void test4_4_17()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Long Serial Number Test17 EE")
+            .withCrls("Long Serial Number CA CRL")
+            .withCACert("Long Serial Number CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.18 Invalid Long Serial Number Test18
+     * <p>
+     * RFC 3280 mandates that certificate users be able to handle serial number values up to 20 octets
+     * long. In this test, the end entity's certificate has a 20 octet serial number and the certificate's serial
+     * number is listed on the corresponding CRL.
+     */
+    public void test4_4_18()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Long Serial Number Test18 EE")
+            .withCrls("Long Serial Number CA CRL")
+            .withCACert("Long Serial Number CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.4.19 Valid Separate Certificate and CRL Keys Test19
+     * <p>
+     * In this test, the intermediate CA uses different keys to sign certificates and CRLs. The Trust
+     * Anchor CA has issued two certificates to the intermediate CA, one for each key. The end entity's
+     * certificate was signed using the intermediate CA's certificate signing key.
+     */
+    // CHECK -- "Trust anchor for certification path not found."
+    public void xtest4_4_19()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Separate Certificate and CRL Keys Test19 EE")
+            .withCrls("Separate Certificate and CRLKeys CRL")
+            .withCACert("SeparateCertificate and CRL Keys CRL Signing Cert")
+            .withCACert("Separate Certificate and CRL Keys Certificate Signing CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.4.20 Invalid Separate Certificate and CRL Keys Test20
+     * <p>
+     * In this test, the intermediate CA uses different keys to sign certificates and CRLs. The Trust
+     * Anchor CA has issued two certificates to the intermediate CA, one for each key. The end entity's
+     * certificate was signed using the intermediate CA's certificate signing key. The CRL issued by the
+     * intermediate CA lists the end entity's certificate as revoked.
+     */
+    // CHECK getting "Trust anchor for certification path not found."
+    public void xtest4_4_20()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Separate Certificate and CRL Keys Test20 EE")
+            .withCrls("Separate Certificate and CRLKeys CRL")
+            .withCACert("SeparateCertificate and CRL Keys CRL Signing Cert")
+            .withCACert("Separate Certificate and CRL Keys Certificate Signing CA Cert")
+            .doExceptionTest(1, "--");
+    }
+
+    /**
+     * 4.4.21 Invalid Separate Certificate and CRL Keys Test21
+     * <p>
+     * In this test, the intermediate CA uses different keys to sign certificates and CRLs. The Trust
+     * Anchor CA has issued two certificates to the intermediate CA, one for each key. The certificate
+     * issued to the intermediate CA's CRL verification key has been revoked. The end entity's certificate
+     * was signed using the intermediate CA's certificate signing key.
+     */
+    // CHECK -- Got: Trust anchor for certification path not found.
+    public void xtest4_4_21()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Separate Certificate and CRL Keys Test21 EE")
+            .withCrls("Separate Certificate and CRLKeys CA2 CRL")
+            .withCACert("SeparateCertificate and CRL Keys CA2 CRL Signing Cert")
+            .withCACert("Separate Certificate and CRL Keys CA2 Certificate Signing CA Cert")
+            .doExceptionTest(1, "--");
+    }
+
+    /**
+     * 4.5.1 Valid Basic Self-Issued Old With New Test1
+     * <p>
+     * In this test, the Trust Anchor CA has issued a certificate to the intermediate CA that contains the
+     * intermediate CA's new public key. The end entity's certificate was signed using the intermediate
+     * CA's old private key, requiring the relying party to use the CA's old-signed-with-new self-issued
+     * certificate in order to validate the end entity's certificate. The intermediate CA issues one CRL,
+     * signed with its new private key, that covers all of the unexpired certificates that it has issued.
+     */
+    public void test4_5_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Basic SelfIssued Old With New Test1 EE")
+            .withCACert("Basic SelfIssued New Key OldWithNew CA Cert")
+            .withCrls("Basic SelfIssued New Key CA CRL")
+            .withCACert("Basic SelfIssued New Key CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.5.2 Invalid Basic Self-Issued Old With New Test2
+     * <p>
+     * In this test, the Trust Anchor CA has issued a certificate to the intermediate CA that contains the
+     * intermediate CA's new public key. The end entity's certificate was signed using the intermediate
+     * CA's old private key, requiring the relying party to use the CA's old-signed-with-new self-issued
+     * certificate in order to validate the end entity's certificate. The intermediate CA issues one CRL,
+     * signed with its new private key, that covers all of the unexpired certificates that it has issued. This
+     * CRL indicates that the end entity's certificate has been revoked.
+     */
+    public void test4_5_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Basic SelfIssued Old With New Test2 EE")
+            .withCACert("Basic SelfIssued New Key OldWithNew CA Cert")
+            .withCrls("Basic SelfIssued New Key CA CRL")
+            .withCACert("Basic SelfIssued New Key CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.5.3 Valid Basic Self-Issued New With Old Test3
+     * <p>
+     * In this test, the Trust Anchor CA has issued a certificate to the intermediate CA that contains the
+     * intermediate CA's old public key. The end entity's certificate and a CRL covering all certificates
+     * issued by the intermediate CA was signed using the intermediate CA's new private key, requiring
+     * the relying party to use the CA's new-signed-with-old self-issued certificate in order to validate
+     * both the end entity's certificate and the intermediate CA's CRL. There is a second CRL, signed
+     * using the intermediate CA's old private key that only covers the new-signed-with-old self-issued
+     * certificate.
+     */
+    public void test4_5_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Basic SelfIssued New With Old Test3 EE")
+            .withCrls("Basic SelfIssued Old Key CACRL")
+            .withCACert("Basic SelfIssued Old Key NewWithOld CA Cert")
+            .withCrls("Basic SelfIssued Old Key SelfIssued CertCRL")
+            .withCACert("Basic SelfIssued Old Key CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.5.4 Valid Basic Self-Issued New With Old Test4
+     * <p>
+     * In this test, the Trust Anchor CA has issued a certificate to the intermediate CA that contains the
+     * intermediate CA's old public key. The end entity's certificate was signed using the intermediate
+     * CA's old private key, so there is no need to use a self-issued certificate to create a certification path
+     * from the Trust Anchor to the end entity. However, the CRL covering all certificates issued by the
+     * intermediate CA was signed using the intermediate CA's new private key, requiring the relying
+     * party to use the CA's new-signed-with-old self-issued certificate in order to validate the
+     * intermediate CA's CRL. This CRL must be validated in order to determine the status of the end
+     * entity's certificate. There is a second CRL, signed using the intermediate CA's old private key that
+     * only covers the new-signed-with-old self-issued certificate.
+     */
+    // CHECK I think it is not using the new-signed-with-old
+    public void xtest4_5_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Basic SelfIssued New With Old Test4 EE")
+            .withCrls("Basic SelfIssued Old Key CACRL")
+            .withCACert("Basic SelfIssued Old Key NewWithOld CA Cert")
+            .withCrls("Basic SelfIssued Old Key SelfIssued CertCRL")
+            .withCACert("Basic SelfIssued Old Key CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.5.5 Invalid Basic Self-Issued New With Old Test5
+     * <p>
+     * In this test, the Trust Anchor CA has issued a certificate to the intermediate CA that contains the
+     * intermediate CA's old public key. The end entity's certificate was signed using the intermediate
+     * CA's old private key, so there is no need to use a self-issued certificate to create a certification path
+     * from the Trust Anchor to the end entity. However, the CRL covering all certificates issued by the
+     * intermediate CA was signed using the intermediate CA's new private key, requiring the relying
+     * party to use the CA's new-signed-with-old self-issued certificate in order to validate the
+     * intermediate CA's CRL. This CRL must be validated in order to determine the status of the end
+     * entity's certificate. There is a second CRL, signed using the intermediate CA's old private key that
+     * only covers the new-signed-with-old self-issued certificate. The end entity's certificate has been
+     * revoked.
+     */
+    // CHECK I think it is not using the new-signed-with-old
+    public void xtest4_5_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Basic SelfIssued New With Old Test5 EE")
+            .withCrls("Basic SelfIssued Old Key CACRL")
+            .withCACert("Basic SelfIssued Old Key NewWithOld CA Cert")
+            .withCrls("Basic SelfIssued Old Key SelfIssued CertCRL")
+            .withCACert("Basic SelfIssued Old Key CA Cert")
+            .doExceptionTest(0, "--");
+    }
+
+    /**
+     * 4.5.6 Valid Basic Self-Issued CRL Signing Key Test6
+     * <p>
+     * In this test, the intermediate CA maintains two key pairs, one for signing certificates and the other
+     * for signing CRLs. The Trust Anchor CA has issued a certificate to the intermediate CA that
+     * contains the intermediate CA's certificate verification public key, and the intermediate CA has
+     * issued a self-issued certificate that contains its CRL verification key. The intermediate CA's
+     * certificate signing private key has been used to sign a CRL that only covers the self-issued
+     * certificate.
+     */
+    // CHECK we may be too strict here, "Intermediate certificate lacks BasicConstraints"
+    public void xtest4_5_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Basic SelfIssued CRL Signing Key Test6 EE")
+            .withCrls("Basic SelfIssued CRL SigningKey CA CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CRL Cert")
+            .withCrls("Basic SelfIssued CRL SigningKey CRL Cert CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.5.7 Invalid Basic Self-Issued CRL Signing Key Test7
+     * <p>
+     * In this test, the intermediate CA maintains two key pairs, one for signing certificates and the other
+     * for signing CRLs. The Trust Anchor CA has issued a certificate to the intermediate CA that
+     * contains the intermediate CA's certificate verification public key, and the intermediate CA has
+     * issued a self-issued certificate that contains its CRL verification key. The intermediate CA's
+     * certificate signing private key has been used to sign a CRL that only covers the self-issued
+     * certificate. The end entity's certificate has been revoked.
+     */
+    // CHECK we may be too strict here, "Intermediate certificate lacks BasicConstraints"
+    public void xtest4_5_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Basic SelfIssued CRL Signing Key Test7 EE")
+            .withCrls("Basic SelfIssued CRL SigningKey CA CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CRL Cert")
+            .withCrls("Basic SelfIssued CRL SigningKey CRL Cert CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CA Cert")
+            .doExceptionTest(1, "--");
+    }
+
+    /**
+     * 4.5.8 Invalid Basic Self-Issued CRL Signing Key Test8
+     * <p>
+     * In this test, the intermediate CA maintains two key pairs, one for signing certificates and the other
+     * for signing CRLs. The Trust Anchor CA has issued a certificate to the intermediate CA that
+     * contains the intermediate CA's certificate verification public key, and the intermediate CA has
+     * issued a self-issued certificate that contains its CRL verification key. The intermediate CA's
+     * certificate signing private key has been used to sign a CRL that only covers the self-issued
+     * certificate. The end entity's certificate was signed using the CRL signing key.
+     */
+    public void test4_5_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Basic SelfIssued CRL Signing Key Test8 EE")
+            .withCrls("Basic SelfIssued CRL SigningKey CA CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CRL Cert")
+            .withCrls("Basic SelfIssued CRL SigningKey CRL Cert CRL")
+            .withCACert("Basic SelfIssued CRL Signing Key CA Cert")
+            .doExceptionTest(1, "Intermediate certificate lacks BasicConstraints");
+    }
+
+    /**
+     * 4.6.1 Invalid Missing basicConstraints Test1
+     * <p>
+     * In this test, the intermediate certificate does not have a basicConstraints extension.
+     */
+    public void test4_6_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Missing basicConstraints Test1 EE")
+            .withCrls("Missing basicConstraints CA CRL")
+            .withCACert("Missing basicConstraints CA Cert")
+            .doExceptionTest(1, "Intermediate certificate lacks BasicConstraints");
+    }
+
+    /**
+     * 4.6.2 Invalid cA False Test2
+     * <p>
+     * In this test, the basicConstraints extension is present in the intermediate certificate and is marked
+     * critical, but the cA component is false, indicating that the subject public key may not be used to
+     * verify signatures on certificates.
+     */
+    public void test4_6_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cA False Test2 EE")
+            .withCrls("basicConstraints Critical cA FalseCA CRL")
+            .withCACert("basicConstraints Critical cA False CA Cert")
+            .doExceptionTest(1, "Not a CA certificate");
+    }
+
+    /**
+     * 4.6.3 Invalid cA False Test3
+     * <p>
+     * In this test, the basicConstraints extension is present in the intermediate certificate and is marked
+     * not critical, but the cA component is false, indicating that the subject public key may not be used to
+     * verify signatures on certificates. As specified in section 8.4.2.1 of X.509, the application must
+     * reject the path either because the application does not recognize the basicConstraints extension or
+     * because cA is set to false.
+     */
+    public void test4_6_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cA False Test3 EE")
+            .withCrls("basicConstraints Not CriticalcA False CA CRL")
+            .withCACert("basicConstraints Not Critical cA False CA Cert")
+            .doExceptionTest(1, "Not a CA certificate");
+    }
+
+    /**
+     * 4.6.4 Valid basicConstraints Not Critical Test4
+     * <p>
+     * In this test, the basicConstraints extension is present in the intermediate certificate and the cA
+     * component is true, but the extension is marked not critical.
+     */
+    public void test4_6_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid basicConstraints Not Critical Test4 EE")
+            .withCrls("basicConstraints Not Critical CA CRL")
+            .withCACert("basicConstraints Not Critical CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.5 Invalid pathLenConstraint Test5
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional intermediate certificates in the path). This is
+     * followed by a second intermediate certificate and a end entity certificate.
+     */
+    public void test4_6_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test5 EE")
+            .withCrls("pathLenConstraint0 subCA CRL")
+            .withCACert("pathLenConstraint0 subCA Cert")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.6 Invalid pathLenConstraint Test6
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional intermediate certificates in the path). This is
+     * followed by two more CA certificates, the second of which is the end certificate in the path.
+     */
+    public void test4_6_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test6 EE")
+            .withCrls("pathLenConstraint0 subCA CRL")
+            .withCACert("pathLenConstraint0 subCA Cert")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.7 Valid pathLenConstraint Test7
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional intermediate certificates in the path). This is
+     * followed by the end entity certificate.
+     */
+    public void test4_6_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid pathLenConstraint Test7 EE")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.8 Valid pathLenConstraint Test8
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional intermediate certificates in the path). This is
+     * followed by the end entity certificate, which is a CA certificate.
+     */
+    public void test4_6_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid pathLenConstraint Test8 EE")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.9 Invalid pathLenConstraint Test9
+     * <p>
+     * This test consists of a certification path of length 4. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 0, and the third a
+     * pathLenConstraint of 0. The fourth certificate is an end entity certificate.
+     */
+    public void test4_6_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test9 EE")
+            .withCrls("pathLenConstraint6 subsubCA00 CRL")
+            .withCACert("pathLenConstraint6 subsubCA00 Cert")
+            .withCrls("pathLenConstraint6 subCA0 CRL")
+            .withCACert("pathLenConstraint6 subCA0 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.10 Invalid pathLenConstraint Test10
+     * <p>
+     * This test consists of a certification path of length 4. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 0, and the third a
+     * pathLenConstraint of 0. The end entity certificate is a CA certificate.
+     */
+    public void test4_6_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test10 EE")
+            .withCrls("pathLenConstraint6 subsubCA00 CRL")
+            .withCACert("pathLenConstraint6 subsubCA00 Cert")
+            .withCrls("pathLenConstraint6 subCA0 CRL")
+            .withCACert("pathLenConstraint6 subCA0 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.11 Invalid pathLenConstraint Test11
+     * <p>
+     * This test consists of a certification path of length 5. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 1, and the third a
+     * pathLenConstraint of 1. The fourth certificate does not include a pathLenConstraint. The fifth
+     * certificate is an end entity certificate.
+     */
+    public void test4_6_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test11 EE")
+            .withCrls("pathLenConstraint6subsubsubCA11X CRL")
+            .withCACert("pathLenConstraint6 subsubsubCA11X Cert")
+            .withCrls("pathLenConstraint6 subsubCA11 CRL")
+            .withCACert("pathLenConstraint6 subsubCA11 Cert")
+            .withCrls("pathLenConstraint6 subCA1 CRL")
+            .withCACert("pathLenConstraint6 subCA1 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.12 Invalid pathLenConstraint Test12
+     * <p>
+     * This test consists of a certification path of length 5. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 1, and the third a
+     * pathLenConstraint of 1. The fourth certificate does not include a pathLenConstraint. The end
+     * entity certificate is a CA certificate.
+     */
+    public void test4_6_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid pathLenConstraint Test12 EE")
+            .withCrls("pathLenConstraint6subsubsubCA11X CRL")
+            .withCACert("pathLenConstraint6 subsubsubCA11X Cert")
+            .withCrls("pathLenConstraint6 subsubCA11 CRL")
+            .withCACert("pathLenConstraint6 subsubCA11 Cert")
+            .withCrls("pathLenConstraint6 subCA1 CRL")
+            .withCACert("pathLenConstraint6 subCA1 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.13 Valid pathLenConstraint Test13
+     * <p>
+     * This test consists of a certification path of length 5. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 4, and the third a
+     * pathLenConstraint of 1. The fourth certificate does not include a pathLenConstraint. The fifth
+     * certificate is an end entity certificate.
+     */
+    public void test4_6_13()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid pathLenConstraint Test13 EE")
+            .withCrls("pathLenConstraint6subsubsubCA41X CRL")
+            .withCACert("pathLenConstraint6 subsubsubCA41X Cert")
+            .withCrls("pathLenConstraint6 subsubCA41 CRL")
+            .withCACert("pathLenConstraint6 subsubCA41 Cert")
+            .withCrls("pathLenConstraint6 subCA4 CRL")
+            .withCACert("pathLenConstraint6 subCA4 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.14 Valid pathLenConstraint Test14
+     * <p>
+     * This test consists of a certification path of length 5. The first certificate in the path includes a
+     * pathLenConstraint of 6, the second a pathLenConstraint of 4, and the third a
+     * pathLenConstraint of 1. The fourth certificate does not include a pathLenConstraint. The end
+     * entity certificate is a CA certificate.
+     */
+    public void test4_6_14()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid pathLenConstraint Test14 EE")
+            .withCrls("pathLenConstraint6subsubsubCA41X CRL")
+            .withCACert("pathLenConstraint6 subsubsubCA41X Cert")
+            .withCrls("pathLenConstraint6 subsubCA41 CRL")
+            .withCACert("pathLenConstraint6 subsubCA41 Cert")
+            .withCrls("pathLenConstraint6 subCA4 CRL")
+            .withCACert("pathLenConstraint6 subCA4 Cert")
+            .withCrls("pathLenConstraint6 CA CRL")
+            .withCACert("pathLenConstraint6 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.15 Valid Self-Issued pathLenConstraint Test15
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional non-self-issued intermediate certificates in the
+     * path). This is followed by a self-issued certificate and the end entity certificate.
+     * 32
+     */
+    public void test4_6_15()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid SelfIssued pathLenConstraint Test15 EE")
+            .withCACert("pathLenConstraint0 SelfIssued CA Cert")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.6.16 Invalid Self-Issued pathLenConstraint Test16
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 0 (allowing 0 additional non-self-issued intermediate certificates in the
+     * path). This is followed by a self-issued certificate, an non-self-issued certificate, and the end entity
+     * certificate.
+     */
+    public void test4_6_16()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued pathLenConstraint Test16 EE")
+            .withCrls("pathLenConstraint0 subCA2 CRL")
+            .withCACert("pathLenConstraint0 subCA2 Cert")
+            .withCACert("pathLenConstraint0 SelfIssued CA Cert")
+            .withCrls("pathLenConstraint0 CA CRL")
+            .withCACert("pathLenConstraint0 CA Cert")
+            .doExceptionTest(1, "Max path length not greater than zero");
+    }
+
+    /**
+     * 4.6.17 Valid Self-Issued pathLenConstraint Test17
+     * <p>
+     * In this test, the first certificate in the path includes a basicConstraints extension with a
+     * pathLenConstraint of 1 (allowing 1 additional non-self-issued intermediate certificate in the
+     * path). This is followed by a self-issued certificate, a non-self-issued certificate, another self-issued
+     * certificate, and the end entity certificate.
+     */
+    public void test4_6_17()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid SelfIssued pathLenConstraint Test17 EE")
+            .withCACert("pathLenConstraint1 SelfIssued subCA Cert")
+            .withCrls("pathLenConstraint1 subCA CRL")
+            .withCACert("pathLenConstraint1 subCA Cert")
+            .withCACert("pathLenConstraint1 SelfIssued CA Cert")
+            .withCrls("pathLenConstraint1 CA CRL")
+            .withCACert("pathLenConstraint1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.7.1 Invalid keyUsage Critical keyCertSign False Test1
+     * <p>
+     * In this test, the intermediate certificate includes a critical keyUsage extension in which
+     * keyCertSign is false.
+     */
+    public void test4_7_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid keyUsage Critical keyCertSign False Test1 EE")
+            .withCrls("keyUsage Critical keyCertSignFalse CA CRL")
+            .withCACert("keyUsage Critical keyCertSign False CA Cert")
+            .doExceptionTest(1, "Issuer certificate keyusage extension is critical and does not permit key signing.");
+    }
+
+    /**
+     * 4.7.2 Invalid keyUsage Not Critical keyCertSign False Test2
+     * <p>
+     * In this test, the intermediate certificate includes a non-critical keyUsage extension in which
+     * keyCertSign is false.
+     */
+    public void test4_7_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid keyUsage Not Critical keyCertSign False Test2 EE")
+            .withCrls("keyUsage Not CriticalkeyCertSign False CA CRL")
+            .withCACert("keyUsage Not Critical keyCertSign False CA Cert")
+            .doExceptionTest(1, "Issuer certificate keyusage extension is critical and does not permit key signing.");
+    }
+
+    /**
+     * 4.7.3 Valid keyUsage Not Critical Test3
+     * <p>
+     * In this test, the intermediate certificate includes a non-critical keyUsage extension.
+     * 34
+     */
+    public void test4_7_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid keyUsage Not Critical Test3 EE")
+            .withCrls("keyUsage Not Critical CA CRL")
+            .withCACert("keyUsage Not Critical CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.7.4 Invalid keyUsage Critical cRLSign False Test4
+     * <p>
+     * In this test, the intermediate certificate includes a critical keyUsage extension in which cRLSign
+     * is false.
+     */
+    public void test4_7_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid keyUsage Critical cRLSign False Test4 EE")
+            .withCrls("keyUsage Critical cRLSign False CACRL")
+            .withCACert("keyUsage Critical cRLSign False CA Cert")
+            .doExceptionTest(0, "Issuer certificate key usage extension does not permit CRL signing.");
+    }
+
+    /**
+     * 4.7.5 Invalid keyUsage Not Critical cRLSign False Test5
+     * <p>
+     * In this test, the intermediate certificate includes a non-critical keyUsage extension in which
+     * cRLSign is false.
+     */
+    public void test4_7_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid keyUsage Not Critical cRLSign False Test5 EE")
+            .withCrls("keyUsage Not Critical cRLSignFalse CA CRL")
+            .withCACert("keyUsage Not Critical cRLSign False CA Cert")
+            .doExceptionTest(0, "Issuer certificate key usage extension does not permit CRL signing.");
+    }
+
+    /**
+     * 4.8.1 All Certificates Same Policy Test1
+     * <p>
+     * In this test, every certificate in the path asserts the same policy, NIST-test-policy-1. The
+     * certification path in this test is the same certification path as in Valid Signatures Test1. If
+     * possible, it is recommended that the certification path in this test be validated using the following
+     * inputs:
+     * 1. default settings, but with initial-explicit-policy set. The path should validate
+     * successfully.
+     * 2. default settings, but with initial-explicit-policy set and initial-policy-set =
+     * {NIST-test-policy-1}. The path should validate successfully.
+     * 3. default settings, but with initial-explicit-policy set and initial-policy-set =
+     * {NIST-test-policy-2}. The path should not validate successfully.
+     * 4. default settings, but with initial-explicit-policy set and initial-policy-set =
+     * {NIST-test-policy-1, NIST-test-policy-2}. The path should validate
+     * successfully.
+     */
+    public void test4_8_1()
+        throws Exception
+    {
+
+        // 1
+        new PKITSTest()
+            .withEndEntity("Valid Certificate Path Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true).doTest();
+
+
+        // 2
+        new PKITSTest()
+            .withEndEntity("Valid Certificate Path Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true)
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        // 3
+        new PKITSTest()
+            .withEndEntity("Valid Certificate Path Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true)
+            .withPolicyByName("NIST-test-policy-2")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+
+
+        // 4
+        new PKITSTest()
+            .withEndEntity("Valid Certificate Path Test1 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true)
+            .withPolicyByName("NIST-test-policy-1", "NIST-test-policy-2")
+            .doTest();
+
+
+    }
+
+    /**
+     * 4.8.2 All Certificates No Policies Test2
+     * <p>
+     * In this test, the certificatePolicies extension is omitted from every certificate in the path. If
+     * possible, it is recommended that the certification path in this test be validated using the following
+     * inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-explicit-policy set . The path should not validate
+     * successfully.
+     */
+    public void test4_8_2()
+        throws Exception
+    {
+        // 1
+        new PKITSTest()
+            .withEndEntity("All Certificates No Policies Test2 EE")
+            .withCrls("No Policies CA CRL")
+            .withCACert("No Policies CA Cert")
+            .doTest();
+
+        // 2
+        new PKITSTest()
+            .withEndEntity("All Certificates No Policies Test2 EE")
+            .withCrls("No Policies CA CRL")
+            .withCACert("No Policies CA Cert")
+            .withExplicitPolicyRequired(true)
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.3 Different Policies Test3
+     * <p>
+     * In this test, every certificate in the path asserts the same certificate policy except the first certificate
+     * in the path. If possible, it is recommended that the certification path in this test be validated using
+     * the following inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-explicit-policy set . The path should not validate
+     * successfully.
+     * 3. default settings, but with initial-explicit-policy set and initial-policy-set =
+     * {NIST-test-policy-1, NIST-test-policy-2}. The path should not validate
+     * successfully.
+     */
+    public void test4_8_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test3 EE")
+            .withCrls("Policies P2 subCA CRL")
+            .withCACert("Policies P2 subCA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+
+
+        new PKITSTest()
+            .withEndEntity("Different Policies Test3 EE")
+            .withCrls("Policies P2 subCA CRL")
+            .withCACert("Policies P2 subCA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true)
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+
+        new PKITSTest()
+            .withEndEntity("Different Policies Test3 EE")
+            .withCrls("Policies P2 subCA CRL")
+            .withCACert("Policies P2 subCA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withExplicitPolicyRequired(true)
+            .withPolicyByName("NIST-test-policy-1", "NIST-test-policy-2")
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.4 Different Policies Test4
+     * <p>
+     * In this test, every certificate in the path asserts the same certificate policy except the end entity
+     * certificate.
+     */
+    public void test4_8_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test4 EE")
+            .withCrls("Good subCA CRL")
+            .withCACert("Good subCA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.5 Different Policies Test5
+     * <p>
+     * In this test, every certificate in the path except the second certificate asserts the same policy.
+     */
+    public void test4_8_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test5 EE")
+            .withCrls("Policies P2 subCA2 CRL")
+            .withCACert("Policies P2 subCA2 Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.6 Overlapping Policies Test6
+     * <p>
+     * The following path is such that the intersection of certificate policies among all the certificates has
+     * exactly one policy, NIST-test-policy-1. The final certificate in the path is a CA certificate. If
+     * possible, it is recommended that the certification path in this test be validated using the following
+     * inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 3. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should not validate successfully.
+     */
+    public void test4_8_6()
+        throws Exception
+    {
+        // 1
+        new PKITSTest()
+            .withEndEntity("Overlapping Policies Test6 EE")
+            .withCrls("Policies P1234 subsubCAP123P12CRL")
+            .withCACert("Policies P1234 subsubCAP123P12 Cert")
+            .withCrls("Policies P1234 subCAP123 CRL")
+            .withCACert("Policies P1234 subCAP123 Cert")
+            .withCrls("Policies P1234 CA CRL")
+            .withCACert("Policies P1234 CA Cert")
+            .doTest();
+
+        // 2
+        new PKITSTest()
+            .withEndEntity("Overlapping Policies Test6 EE")
+            .withCrls("Policies P1234 subsubCAP123P12CRL")
+            .withCACert("Policies P1234 subsubCAP123P12 Cert")
+            .withCrls("Policies P1234 subCAP123 CRL")
+            .withCACert("Policies P1234 subCAP123 Cert")
+            .withCrls("Policies P1234 CA CRL")
+            .withCACert("Policies P1234 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+
+        // 3
+        new PKITSTest()
+            .withEndEntity("Overlapping Policies Test6 EE")
+            .withCrls("Policies P1234 subsubCAP123P12CRL")
+            .withCACert("Policies P1234 subsubCAP123P12 Cert")
+            .withCrls("Policies P1234 subCAP123 CRL")
+            .withCACert("Policies P1234 subCAP123 Cert")
+            .withCrls("Policies P1234 CA CRL")
+            .withCACert("Policies P1234 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+
+
+    }
+
+    /**
+     * 4.8.7 Different Policies Test7
+     * <p>
+     * The following path is such that the intersection of certificate policies among all the certificates is
+     * empty. The final certificate in the path is a CA certificate.
+     */
+    public void test4_8_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test7 EE")
+            .withCrls("Policies P123 subsubCAP12P1 CRL")
+            .withCACert("Policies P123 subsubCAP12P1 Cert")
+            .withCrls("Policies P123 subCAP12 CRL")
+            .withCACert("Policies P123 subCAP12 Cert")
+            .withCrls("Policies P123 CA CRL")
+            .withCACert("Policies P123 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.8 Different Policies Test8
+     * <p>
+     * The following path is such that the intersection of certificate policies among all the certificates is
+     * empty. The final certificate in the path is a CA certificate.
+     */
+    public void test4_8_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test8 EE")
+            .withCrls("Policies P12 subsubCAP1P2 CRL")
+            .withCACert("Policies P12 subsubCAP1P2 Cert")
+            .withCrls("Policies P12 subCAP1 CRL")
+            .withCACert("Policies P12 subCAP1 Cert")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.9 Different Policies Test9
+     * <p>
+     * The following path is such that the intersection of certificate policies among all the certificates is
+     * empty.
+     */
+    public void test4_8_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test9 EE")
+            .withCrls("Policies P123subsubsubCAP12P2P1 CRL")
+            .withCACert("Policies P123 subsubsubCAP12P2P1 Cert")
+            .withCrls("Policies P123 subsubCAP2P2 CRL")
+            .withCACert("Policies P123 subsubCAP12P2 Cert")
+            .withCrls("Policies P123 subCAP12 CRL")
+            .withCACert("Policies P123 subCAP12 Cert")
+            .withCrls("Policies P123 CA CRL")
+            .withCACert("Policies P123 CA Cert")
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.10 All Certificates Same Policies Test10
+     * <p>
+     * In this test, every certificate in the path asserts the same policies, NIST-test-policy-1 and NISTtest-policy-2. If possible, it is recommended that the certification path in this test be validated
+     * using the following inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 3. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should validate successfully.
+     */
+    public void test4_8_10()
+        throws Exception
+    {
+        // 1
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test10 EE")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .doTest();
+        // 2
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test10 EE")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        // 3
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test10 EE")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doTest();
+    }
+
+    /**
+     * 4.8.11 All Certificates AnyPolicy Test11
+     * <p>
+     * In this test, every certificate in the path asserts the special policy anyPolicy. If possible, it is
+     * recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     */
+    public void test4_8_11()
+        throws Exception
+    {
+        // 2
+        new PKITSTest()
+            .withEndEntity("All Certificates anyPolicy Test11 EE")
+            .withCrls("anyPolicy CA CRL")
+            .withCACert("anyPolicy CA Cert")
+            .doTest();
+
+        // 2
+        new PKITSTest()
+            .withEndEntity("All Certificates anyPolicy Test11 EE")
+            .withCrls("anyPolicy CA CRL")
+            .withCACert("anyPolicy CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+    }
+
+    /**
+     * 4.8.12 Different Policies Test12
+     * <p>
+     * In this test, the path consists of two certificates, each of which asserts a different certificate policy.
+     */
+    public void test4_8_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Different Policies Test12 EE")
+            .withCrls("Policies P3 CA CRL")
+            .withCACert("Policies P3 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.8.13 All Certificates Same Policies Test13
+     * <p>
+     * In this test, every certificate in the path asserts the same policies, NIST-test-policy-1, NIST-testpolicy-2, and NIST-test-policy-3. If possible, it is recommended that the certification path in this
+     * test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should validate successfully.
+     * 3. default settings, but with initial-policy-set = {NIST-test-policy-3}. The path
+     * should validate successfully.
+     */
+    public void test4_8_13()
+        throws Exception
+    {
+        // 1
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test13 EE")
+            .withCrls("Policies P123 CA CRL")
+            .withCACert("Policies P123 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        // 2
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test13 EE")
+            .withCrls("Policies P123 CA CRL")
+            .withCACert("Policies P123 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doTest();
+
+        // 3
+        new PKITSTest()
+            .withEndEntity("All Certificates Same Policies Test13 EE")
+            .withCrls("Policies P123 CA CRL")
+            .withCACert("Policies P123 CA Cert")
+            .withPolicyByName("NIST-test-policy-3")
+            .doTest();
+    }
+
+    /**
+     * 4.8.14 AnyPolicy Test14
+     * <p>
+     * In this test, the intermediate certificate asserts anyPolicy and the end entity certificate asserts
+     * NIST-test-policy-1. If possible, it is recommended that the certification path in this test be
+     * validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should not validate successfully.
+     */
+    public void test4_8_14()
+        throws Exception
+    {
+        // 1
+        new PKITSTest()
+            .withEndEntity("AnyPolicy Test14 EE")
+            .withCrls("anyPolicy CA CRL")
+            .withCACert("anyPolicy CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+        // 2
+        new PKITSTest()
+            .withEndEntity("AnyPolicy Test14 EE")
+            .withCrls("anyPolicy CA CRL")
+            .withCACert("anyPolicy CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.8.15 User Notice Qualifier Test15
+     * <p>
+     * In this test, the path consists of a single certificate. The certificate asserts the policy NIST-testpolicy-1 and includes a user notice policy qualifier.
+     * <p>
+     * Display of user notice beyond CertPath API at the moment.
+     * </p>
+     */
+    public void test4_8_15()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test15 EE")
+            .doTest();
+
+        new PKITSTest()
+               .withPolicyByName("NIST-test-policy-2")
+               .withEndEntity("User Notice Qualifier Test15 EE")
+               .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.8.16 User Notice Qualifier Test16
+     * <p>
+     * In this test, the path consists of an intermediate certificate and an end entity certificate. The
+     * intermediate certificate asserts the policy NIST-test-policy-1. The end entity certificate asserts
+     * both NIST-test-policy-1 and NIST-test-policy-2. Each policy in the end entity certificate has a
+     * different user notice qualifier associated with it.
+     * <p>
+     * Display of user notice beyond CertPath API at the moment.
+     * </p>
+     */
+    public void test4_8_16()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test16 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+    }
+
+    /**
+     * 4.8.17 User Notice Qualifier Test17
+     * <p>
+     * In this test, the path consists of an intermediate certificate and an end entity certificate. The
+     * intermediate certificate asserts the policy NIST-test-policy-1. The end entity certificate asserts
+     * anyPolicy. There is a user notice policy qualifier associated with anyPolicy in the end entity
+     * certificate.
+     * <p>
+     * Display of user notice beyond CertPath API at the moment.
+     * </p>
+     */
+    public void test4_8_17()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test17 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+    }
+
+    /**
+     * 4.8.18 User Notice Qualifier Test18
+     * <p>
+     * In this test, the intermediate certificate asserts policies NIST-test-policy-1 and NIST-test-policy-2.
+     * The end certificate asserts NIST-test-policy-1 and anyPolicy. Each of the policies in the end
+     * entity certificate asserts a different user notice policy qualifier. If possible, it is recommended that
+     * the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully and the qualifier associated with NIST-test-policy-1
+     * in the end entity certificate should be displayed.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should validate successfully and the qualifier associated with anyPolicy in the
+     * end entity certificate should be displayed.
+     * 45
+     * <p>
+     * Display of policy messages beyond CertPath API at the moment.
+     * </p>
+     */
+    public void test4_8_18()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test18 EE")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test18 EE")
+            .withCrls("Policies P12 CA CRL")
+            .withCACert("Policies P12 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doTest();
+    }
+
+    /**
+     * 4.8.19 User Notice Qualifier Test19
+     * <p>
+     * In this test, the path consists of a single certificate. The certificate asserts the policy NIST-testpolicy-1 and includes a user notice policy qualifier. The user notice qualifier contains explicit text
+     * that is longer than 200 bytes.
+     * [RFC 3280 4.2.1.5] Note: While the explicitText has a maximum size of 200 characters,
+     * some non-conforming CAs exceed this limit. Therefore, certificate users SHOULD
+     * gracefully handle explicitText with more than 200 characters.
+     */
+    public void test4_8_19()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("User Notice Qualifier Test19 EE")
+            .doTest();
+    }
+
+    /**
+     * 4.8.20 CPS Pointer Qualifier Test20
+     * <p>
+     * In this test, the path consists of an intermediate certificate and an end entity certificate, both of
+     * which assert the policy NIST-test-policy-1. There is a CPS pointer policy qualifier associated with
+     * NIST-test-policy-1 in the end entity certificate.
+     */
+    public void test4_8_20()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("CPS Pointer Qualifier Test20 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.9.1 Valid RequireExplicitPolicy Test1
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 10. This is followed by three more intermediate certificates and an
+     * end entity certificate. The end entity certificate does not include a certificatePolicies extension.
+     * 47
+     */
+    public void test4_9_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid requireExplicitPolicy Test1 EE")
+            .withCrls("requireExplicitPolicy10subsubsubCA CRL")
+            .withCACert("requireExplicitPolicy10 subsubsubCA Cert")
+            .withCrls("requireExplicitPolicy10 subsubCACRL")
+            .withCACert("requireExplicitPolicy10 subsubCA Cert")
+            .withCrls("requireExplicitPolicy10 subCA CRL")
+            .withCACert("requireExplicitPolicy10 subCA Cert")
+            .withCrls("requireExplicitPolicy10 CA CRL")
+            .withCACert("requireExplicitPolicy10 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.9.2 Valid RequireExplicitPolicy Test2
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 5. This is followed by three more intermediate certificates and an end
+     * entity certificate. The end entity certificate does not include a certificatePolicies extension.
+     */
+    public void test4_9_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid requireExplicitPolicy Test2 EE")
+            .withCrls("requireExplicitPolicy5 subsubsubCACRL")
+            .withCACert("requireExplicitPolicy5 subsubsubCA Cert")
+            .withCrls("requireExplicitPolicy5 subsubCA CRL")
+            .withCACert("requireExplicitPolicy5 subsubCA Cert")
+            .withCrls("requireExplicitPolicy5 subCA CRL")
+            .withCACert("requireExplicitPolicy5 subCA Cert")
+            .withCrls("requireExplicitPolicy5 CA CRL")
+            .withCACert("requireExplicitPolicy5 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.9.3 Invalid RequireExplicitPolicy Test3
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 4. This is followed by three more intermediate certificates and an end
+     * entity certificate. The end entity certificate does not include a certificatePolicies extension.
+     */
+    public void test4_9_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid requireExplicitPolicy Test3 EE")
+            .withCrls("requireExplicitPolicy4 subsubsubCACRL")
+            .withCACert("requireExplicitPolicy4 subsubsubCA Cert")
+            .withCrls("requireExplicitPolicy4 subsubCA CRL")
+            .withCACert("requireExplicitPolicy4 subsubCA Cert")
+            .withCrls("requireExplicitPolicy4 subCA CRL")
+            .withCACert("requireExplicitPolicy4 subCA Cert")
+            .withCrls("requireExplicitPolicy4 CA CRL")
+            .withCACert("requireExplicitPolicy4 CA Cert")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.9.4 Valid RequireExplicitPolicy Test4
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 0. This is followed by three more intermediate certificates and an end
+     * entity certificate.
+     */
+    public void test4_9_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid requireExplicitPolicy Test4 EE")
+            .withCrls("requireExplicitPolicy0 subsubsubCACRL")
+            .withCACert("requireExplicitPolicy0 subsubsubCA Cert")
+            .withCrls("requireExplicitPolicy0 subsubCA CRL")
+            .withCACert("requireExplicitPolicy0 subsubCA Cert")
+            .withCrls("requireExplicitPolicy0 subCA CRL")
+            .withCACert("requireExplicitPolicy0 subCA Cert")
+            .withCrls("requireExplicitPolicy0 CA CRL")
+            .withCACert("requireExplicitPolicy0 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.9.5 Invalid RequireExplicitPolicy Test5
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 7. The second certificate in the path includes a policyConstraints
+     * extension with requireExplicitPolicy set to 2. The third certificate in the path includes a
+     * policyConstraints extension with requireExplicitPolicy set to 4. This is followed by one more
+     * intermediate certificate and an end entity certificate. The end entity certificate does not include a
+     * certificatePolicies extension.
+     */
+    public void test4_9_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid requireExplicitPolicy Test5 EE")
+            .withCrls("requireExplicitPolicy7subsubsubCARE2RE4 CRL")
+            .withCACert("requireExplicitPolicy7 subsubsubCARE2RE4 Cert")
+            .withCrls("requireExplicitPolicy7subsubCARE2RE4 CRL")
+            .withCACert("requireExplicitPolicy7 subsubCARE2RE4 Cert")
+            .withCrls("requireExplicitPolicy7 subCARE2 CRL")
+            .withCACert("requireExplicitPolicy7 subCARE2 Cert")
+            .withCrls("requireExplicitPolicy7 CA CRL")
+            .withCACert("requireExplicitPolicy7 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.9.6 Valid Self-Issued requireExplicitPolicy Test6
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 2. This is followed by a self-issued intermediate certificate and an
+     * end entity certificate. The end entity certificate does not include a certificatePolicies extension.
+     */
+    public void test4_9_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid SelfIssued requireExplicitPolicy Test6 EE")
+            .withCACert("requireExplicitPolicy2 SelfIssued CA Cert")
+            .withCrls("requireExplicitPolicy2 CA CRL")
+            .withCACert("requireExplicitPolicy2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.9.7 Invalid Self-Issued requireExplicitPolicy Test7
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 2. This is followed by a self-issued intermediate certificate, a nonself-issued intermediate certificate, and an end entity certificate. The end entity certificate does not
+     * include a certificatePolicies extension.
+     */
+    public void test4_9_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued requireExplicitPolicy Test7 EE")
+            .withCrls("requireExplicitPolicy2 subCA CRL")
+            .withCACert("requireExplicitPolicy2 subCA Cert")
+            .withCACert("requireExplicitPolicy2 SelfIssued CA Cert")
+            .withCrls("requireExplicitPolicy2 CA CRL")
+            .withCACert("requireExplicitPolicy2 CA Cert")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.9.8 Invalid Self-Issued requireExplicitPolicy Test8
+     * <p>
+     * In this test, the first certificate in the path includes a policyConstraints extension with
+     * requireExplicitPolicy set to 2. This is followed by a self-issued intermediate certificate, a nonself-issued intermediate certificate, a self-issued intermediate certificate, and an end entity
+     * certificate. The end entity certificate does not include a certificatePolicies extension.
+     * 50
+     */
+    public void test4_9_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued requireExplicitPolicy Test8 EE")
+            .withCACert("requireExplicitPolicy2 SelfIssued subCA Cert")
+            .withCrls("requireExplicitPolicy2 subCA CRL")
+            .withCACert("requireExplicitPolicy2 subCA Cert")
+            .withCACert("requireExplicitPolicy2 SelfIssued CA Cert")
+            .withCrls("requireExplicitPolicy2 CA CRL")
+            .withCACert("requireExplicitPolicy2 CA Cert")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.10.1 Valid Policy Mapping Test1
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to
+     * NIST-test-policy-2. The end entity certificate asserts NIST-test-policy-2. If possible, it is
+     * recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should not validate successfully.
+     * 3. default settings, but with initial-policy-mapping-inhibit set. The path should not
+     * validate successfully.
+     */
+    public void test4_10_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test1 EE")
+            .withCrls("Mapping 1to2 CA CRL")
+            .withCACert("Mapping 1to2 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test1 EE")
+            .withCrls("Mapping 1to2 CA CRL")
+            .withCACert("Mapping 1to2 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test1 EE")
+            .withCrls("Mapping 1to2 CA CRL")
+            .withCACert("Mapping 1to2 CA Cert")
+            .withPolicyMappingInhibited(true)
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.10.2 Invalid Policy Mapping Test2
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to
+     * NIST-test-policy-2. The end entity certificate asserts NIST-test-policy-1. If possible, it is
+     * recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings. The path should not validate successfully.
+     * 2. default settings, but with initial-policy-mapping-inhibit set. The path should not
+     * validate successfully.
+     */
+    public void test4_10_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Policy Mapping Test2 EE")
+            .withCrls("Mapping 1to2 CA CRL")
+            .withCACert("Mapping 1to2 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+
+        new PKITSTest()
+            .withEndEntity("Invalid Policy Mapping Test2 EE")
+            .withCrls("Mapping 1to2 CA CRL")
+            .withCACert("Mapping 1to2 CA Cert")
+            .withPolicyMappingInhibited(true)
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.10.3 Valid Policy Mapping Test3
+     * <p>
+     * In this test, the path is valid under NIST-test-policy-2 as a result of policy mappings. If possible,
+     * it is recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should not validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should validate successfully.
+     */
+    public void test4_10_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test3 EE")
+            .withCrls("P12 Mapping 1to3 subsubCA CRL")
+            .withCACert("P12 Mapping 1to3 subsubCA Cert")
+            .withCrls("P12 Mapping 1to3 subCA CRL")
+            .withCACert("P12 Mapping 1to3 subCA Cert")
+            .withCrls("P12 Mapping 1to3 CA CRL")
+            .withCACert("P12 Mapping 1to3 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test3 EE")
+            .withCrls("P12 Mapping 1to3 subsubCA CRL")
+            .withCACert("P12 Mapping 1to3 subsubCA Cert")
+            .withCrls("P12 Mapping 1to3 subCA CRL")
+            .withCACert("P12 Mapping 1to3 subCA Cert")
+            .withCrls("P12 Mapping 1to3 CA CRL")
+            .withCACert("P12 Mapping 1to3 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doTest();
+
+    }
+
+    /**
+     * 4.10.4 Invalid Policy Mapping Test4
+     * <p>
+     * In this test, the policy asserted in the end entity certificate is not in the authorities-constrainedpolicy-set.
+     */
+    public void test4_10_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Policy Mapping Test4 EE")
+            .withCrls("P12 Mapping 1to3 subsubCA CRL")
+            .withCACert("P12 Mapping 1to3 subsubCA Cert")
+            .withCrls("P12 Mapping 1to3 subCA CRL")
+            .withCACert("P12 Mapping 1to3 subCA Cert")
+            .withCrls("P12 Mapping 1to3 CA CRL")
+            .withCACert("P12 Mapping 1to3 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.10.5 Valid Policy Mapping Test5
+     * <p>
+     * In this test, the path is valid under NIST-test-policy-1 as a result of policy mappings. If possible,
+     * it is recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-6}. The path
+     * should not validate successfully.
+     */
+    public void test4_10_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test5 EE")
+            .withCrls("P1 Mapping 1to234 subCA CRL")
+            .withCACert("P1 Mapping 1to234 subCA Cert")
+            .withCrls("P1 Mapping 1to234 CA CRL")
+            .withCACert("P1 Mapping 1to234 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test5 EE")
+            .withCrls("P1 Mapping 1to234 subCA CRL")
+            .withCACert("P1 Mapping 1to234 subCA Cert")
+            .withCrls("P1 Mapping 1to234 CA CRL")
+            .withCACert("P1 Mapping 1to234 CA Cert")
+            .withPolicyByName("NIST-test-policy-6")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.10.6 Valid Policy Mapping Test6
+     * <p>
+     * In this test, the path is valid under NIST-test-policy-1 as a result of policy mappings. If possible,
+     * it is recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-6}. The path
+     * should not validate successfully.
+     */
+    public void test4_10_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test6 EE")
+            .withCrls("P1 Mapping 1to234 subCA CRL")
+            .withCACert("P1 Mapping 1to234 subCA Cert")
+            .withCrls("P1 Mapping 1to234 CA CRL")
+            .withCACert("P1 Mapping 1to234 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test6 EE")
+            .withCrls("P1 Mapping 1to234 subCA CRL")
+            .withCACert("P1 Mapping 1to234 subCA Cert")
+            .withCrls("P1 Mapping 1to234 CA CRL")
+            .withCACert("P1 Mapping 1to234 CA Cert")
+            .withPolicyByName("NIST-test-policy-6")
+            .doExceptionTest(-1, "Path processing failed on policy.");
+    }
+
+    /**
+     * 4.10.7 Invalid Mapping From anyPolicy Test7
+     * <p>
+     * In this test, the intermediate certificate includes a policyMappings extension that includes a
+     * mapping in which the issuerDomainPolicy is anyPolicy. The intermediate certificate also
+     * includes a critical policyConstraints extension with requireExplicitPolicy set to 0.
+     * [RFC 3280 6.1.4] (a) If a policy mapping extension is present, verify that the special
+     * value anyPolicy does not appear as an issuerDomainPolicy or a subjectDomainPolicy.
+     */
+    public void test4_10_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Mapping From anyPolicy Test7 EE")
+            .withCrls("Mapping From anyPolicy CA CRL")
+            .withCACert("Mapping From anyPolicy CA Cert")
+            .doExceptionTest(1, "IssuerDomainPolicy is anyPolicy");
+    }
+
+    /**
+     * 4.10.8 Invalid Mapping To anyPolicy Test8
+     * <p>
+     * In this test, the intermediate certificate includes a policyMappings extension that includes a
+     * mapping in which the subjectDomainPolicy is anyPolicy. The intermediate certificate also
+     * includes a critical policyConstraints extension with requireExplicitPolicy set to 0.
+     * [RFC 3280 6.1.4] (a) If a policy mapping extension is present, verify that the special
+     * value anyPolicy does not appear as an issuerDomainPolicy or a subjectDomainPolicy.
+     */
+    public void test4_10_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Mapping To anyPolicy Test8 EE")
+            .withCrls("Mapping To anyPolicy CA CRL")
+            .withCACert("Mapping To anyPolicy CA Cert")
+            .doExceptionTest(1, "SubjectDomainPolicy is anyPolicy");
+    }
+
+    /**
+     * 4.10.9 Valid Policy Mapping Test9
+     * <p>
+     * In this test, the intermediate certificate asserts anyPolicy and maps NIST-test-policy-1 to NISTtest-policy-2. The end entity certificate asserts NIST-test-policy-1.
+     * 55
+     */
+    public void test4_10_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test9 EE")
+            .withCrls("PanyPolicy Mapping 1to2 CA CRL")
+            .withCACert("PanyPolicy Mapping 1to2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.10.10 Invalid Policy Mapping Test10
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1. The second intermediate
+     * certificate asserts anyPolicy and maps NIST-test-policy-1 to NIST-test-policy-2. The end entity
+     * certificate asserts NIST-test-policy-1.
+     */
+    public void test4_10_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Policy Mapping Test10 EE")
+            .withCrls("Good subCA PanyPolicyMapping 1to2 CA CRL")
+            .withCACert("Good subCA PanyPolicy Mapping 1to2 CA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.10.11 Valid Policy Mapping Test11
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1. The second intermediate
+     * certificate asserts anyPolicy and maps NIST-test-policy-1 to NIST-test-policy-2. The end entity
+     * certificate asserts NIST-test-policy-2.
+     */
+    public void test4_10_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test11 EE")
+            .withCrls("Good subCA PanyPolicyMapping 1to2 CA CRL")
+            .withCACert("Good subCA PanyPolicy Mapping 1to2 CA Cert")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.10.12 Valid Policy Mapping Test12
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and
+     * maps NIST-test-policy-1 to NIST-test-policy-3. The end entity certificate asserts anyPolicy and
+     * NIST-test-policy-3, each with a different user notice policy qualifier. If possible, it is
+     * recommended that the certification path in this test be validated using the following inputs:
+     * 1. default settings, but with initial-policy-set = {NIST-test-policy-1}. The path
+     * should validate successfully and the application should display the user notice
+     * associated with NIST-test-policy-3 in the end entity certificate.
+     * 2. default settings, but with initial-policy-set = {NIST-test-policy-2}. The path
+     * should validate successfully and the application should display the user notice
+     * associated with anyPolicy in the end entity certificate.
+     */
+    public void test4_10_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test12 EE")
+            .withCrls("P12 Mapping 1to3 CA CRL")
+            .withCACert("P12 Mapping 1to3 CA Cert")
+            .withPolicyByName("NIST-test-policy-1")
+            .doTest();
+
+
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test12 EE")
+            .withCrls("P12 Mapping 1to3 CA CRL")
+            .withCACert("P12 Mapping 1to3 CA Cert")
+            .withPolicyByName("NIST-test-policy-2")
+            .doTest();
+
+
+    }
+
+    /**
+     * 4.10.13 Valid Policy Mapping Test13
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and anyPolicy and maps NISTtest-policy-1 to NIST-test-policy-2. There is a user notice policy qualifier associated with each of
+     * 57
+     * the policies. The end entity certificate asserts NIST-test-policy-2.
+     */
+    public void test4_10_13()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test13 EE")
+            .withCrls("P1anyPolicy Mapping 1to2 CA CRL")
+            .withCACert("P1anyPolicy Mapping 1to2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.10.14 Valid Policy Mapping Test14
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and anyPolicy and maps NISTtest-policy-1 to NIST-test-policy-2. There is a user notice policy qualifier associated with each of
+     * the policies. The end entity certificate asserts NIST-test-policy-1.
+     */
+    public void test4_10_14()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Policy Mapping Test14 EE")
+            .withCrls("P1anyPolicy Mapping 1to2 CA CRL")
+            .withCACert("P1anyPolicy Mapping 1to2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.11.1 Invalid inhibitPolicyMapping Test1
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 0. The second intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The end
+     * entity certificate asserts NIST-test-policy-1 and NIST-test-policy-2.
+     */
+    public void test4_11_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitPolicyMapping Test1 EE")
+            .withCrls("inhibitPolicyMapping0 subCA CRL")
+            .withCACert("inhibitPolicyMapping0 subCA Cert")
+            .withCrls("inhibitPolicyMapping0 CA CRL")
+            .withCACert("inhibitPolicyMapping0 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.2 Valid inhibitPolicyMapping Test2
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and
+     * includes a policyConstraints extension with inhibitPolicyMapping set to 1. The second
+     * intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and maps NIST-testpolicy-1 to NIST-test-policy-3 and NIST-test-policy-2 to NIST-test-policy-4. The end entity
+     * certificate asserts NIST-test-policy-3.
+     * 59
+     */
+    public void test4_11_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid inhibitPolicyMapping Test2 EE")
+            .withCrls("inhibitPolicyMapping1 P12 subCACRL")
+            .withCACert("inhibitPolicyMapping1 P12 subCA Cert")
+            .withCrls("inhibitPolicyMapping1 P12 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.11.3 Invalid inhibitPolicyMapping Test3
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and
+     * includes a policyConstraints extension with inhibitPolicyMapping set to 1. The second
+     * intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and maps NIST-testpolicy-1 to NIST-test-policy-3 and NIST-test-policy-2 to NIST-test-policy-4. The third
+     * intermediate certificate asserts NIST-test-policy-3 and NIST-test-policy-4 and maps NIST-testpolicy-3 to NIST-test-policy-5. The end entity certificate asserts NIST-test-policy-5.
+     */
+    public void test4_11_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitPolicyMapping Test3 EE")
+            .withCrls("inhibitPolicyMapping1 P12subsubCA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 subsubCA Cert")
+            .withCrls("inhibitPolicyMapping1 P12 subCACRL")
+            .withCACert("inhibitPolicyMapping1 P12 subCA Cert")
+            .withCrls("inhibitPolicyMapping1 P12 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.4 Valid inhibitPolicyMapping Test4
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and
+     * includes a policyConstraints extension with inhibitPolicyMapping set to 1. The second
+     * intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and maps NIST-testpolicy-1 to NIST-test-policy-3 and NIST-test-policy-2 to NIST-test-policy-4. The third
+     * intermediate certificate asserts NIST-test-policy-3 and NIST-test-policy-4 and maps NIST-testpolicy-3 to NIST-test-policy-5. The end entity certificate asserts NIST-test-policy-4.
+     * 60
+     */
+    public void test4_11_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid inhibitPolicyMapping Test4 EE")
+            .withCrls("inhibitPolicyMapping1 P12subsubCA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 subsubCA Cert")
+            .withCrls("inhibitPolicyMapping1 P12 subCACRL")
+            .withCACert("inhibitPolicyMapping1 P12 subCA Cert")
+            .withCrls("inhibitPolicyMapping1 P12 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.11.5 Invalid inhibitPolicyMapping Test5
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 5. The second intermediate
+     * certificate asserts NIST-test-policy-1 and includes a policyConstraints extension with
+     * inhibitPolicyMapping set to 1. The third intermediate certificate asserts NIST-test-policy-1. The
+     * fourth intermediate certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NISTtest-policy-2. The end entity certificate asserts NIST-test-policy-2.
+     */
+    public void test4_11_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitPolicyMapping Test5 EE")
+            .withCrls("inhibitPolicyMapping5subsubsubCA CRL")
+            .withCACert("inhibitPolicyMapping5 subsubsubCA Cert")
+            .withCrls("inhibitPolicyMapping5 subsubCA CRL")
+            .withCACert("inhibitPolicyMapping5 subsubCA Cert")
+            .withCrls("inhibitPolicyMapping5 subCA CRL")
+            .withCACert("inhibitPolicyMapping5 subCA Cert")
+            .withCrls("inhibitPolicyMapping5 CA CRL")
+            .withCACert("inhibitPolicyMapping5 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.6 Invalid inhibitPolicyMapping Test6
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and
+     * includes a policyConstraints extension with inhibitPolicyMapping set to 1. The second
+     * intermediate certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 5. The third intermediate
+     * certificate asserts NIST-test-policy-1 and NIST-test-policy-2 and maps NIST-test-policy-1 to
+     * NIST-test-policy-3. The end entity certificate asserts NIST-test-policy-3.
+     * 61
+     */
+    public void test4_11_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitPolicyMapping Test6 EE")
+            .withCrls("inhibitPolicyMapping1 P12subsubCAIPM5 CRL")
+            .withCACert("inhibitPolicyMapping1 P12 subsubCAIPM5 Cert")
+            .withCrls("inhibitPolicyMapping1 P12subCAIPM5 CRL")
+            .withCACert("inhibitPolicyMapping1 P12 subCAIPM5 Cert")
+            .withCrls("inhibitPolicyMapping1 P12 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P12 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.7 Valid Self-Issued inhibitPolicyMapping Test7
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 1. The second intermediate
+     * certificate is a self-issued certificate that asserts NIST-test-policy-1. The third intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The end
+     * entity certificate asserts NIST-test-policy-2.
+     */
+    public void test4_11_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid SelfIssued inhibitPolicyMapping Test7 EE")
+            .withCrls("inhibitPolicyMapping1 P1 subCA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 subCA Cert")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued CA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.11.8 Invalid Self-Issued inhibitPolicyMapping Test8
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 1. The second intermediate
+     * certificate is a self-issued certificate that asserts NIST-test-policy-1. The third intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The
+     * fourth intermediate certificate asserts NIST-test-policy-2 and maps NIST-test-policy-2 to NISTtest-policy-3. The end entity certificate asserts NIST-test-policy-3.
+     * 62
+     */
+    public void test4_11_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitPolicyMapping Test8 EE")
+            .withCrls("inhibitPolicyMapping1 P1 subsubCACRL")
+            .withCACert("inhibitPolicyMapping1 P1 subsubCA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 subCA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 subCA Cert")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued CA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.9 Invalid Self-Issued inhibitPolicyMapping Test9
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 1. The second intermediate
+     * certificate is a self-issued certificate that asserts NIST-test-policy-1. The third intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The
+     * fourth intermediate certificate asserts NIST-test-policy-2 and maps NIST-test-policy-2 to NISTtest-policy-3. The end entity certificate asserts NIST-test-policy-2.
+     */
+    public void test4_11_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitPolicyMapping Test9 EE")
+            .withCrls("inhibitPolicyMapping1 P1 subsubCACRL")
+            .withCACert("inhibitPolicyMapping1 P1 subsubCA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 subCA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 subCA Cert")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued CA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.10 Invalid Self-Issued inhibitPolicyMapping Test10
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 1. The second intermediate
+     * certificate is a self-issued certificate that asserts NIST-test-policy-1. The third intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The
+     * fourth intermediate certificate is a self-issued certificate that asserts NIST-test-policy-2 and maps
+     * NIST-test-policy-2 to NIST-test-policy-3. The end entity certificate asserts NIST-test-policy-3.
+     * 63
+     */
+    public void test4_11_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitPolicyMapping Test10 EE")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued subCA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 subCA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 subCA Cert")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued CA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.11.11 Invalid Self-Issued inhibitPolicyMapping Test11
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes a
+     * policyConstraints extension with inhibitPolicyMapping set to 1. The second intermediate
+     * certificate is a self-issued certificate that asserts NIST-test-policy-1. The third intermediate
+     * certificate asserts NIST-test-policy-1 and maps NIST-test-policy-1 to NIST-test-policy-2. The
+     * fourth intermediate certificate is a self-issued certificate that asserts NIST-test-policy-2 and maps
+     * NIST-test-policy-2 to NIST-test-policy-3. The end entity certificate asserts NIST-test-policy-2.
+     */
+    public void test4_11_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitPolicyMapping Test11 EE")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued subCA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 subCA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 subCA Cert")
+            .withCACert("inhibitPolicyMapping1 P1 SelfIssued CA Cert")
+            .withCrls("inhibitPolicyMapping1 P1 CA CRL")
+            .withCACert("inhibitPolicyMapping1 P1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.1 Invalid inhibitAnyPolicy Test1
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 0. The end entity certificate asserts anyPolicy.
+     */
+    public void test4_12_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitAnyPolicy Test1 EE")
+            .withCrls("inhibitAnyPolicy0 CA CRL")
+            .withCACert("inhibitAnyPolicy0 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.2 Valid inhibitAnyPolicy Test2
+     * <p>
+     * In this test, the intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 0. The end entity certificate asserts anyPolicy and NIST-testpolicy-1.
+     */
+    public void test4_12_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid inhibitAnyPolicy Test2 EE")
+            .withCrls("inhibitAnyPolicy0 CA CRL")
+            .withCACert("inhibitAnyPolicy0 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.12.3 inhibitAnyPolicy Test3
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate asserts anyPolicy. The
+     * end entity certificate asserts NIST-test-policy-1. If possible, it is recommended that the
+     * certification path in this test be validated using the following inputs:
+     * 1. default settings. The path should validate successfully.
+     * 2. default settings, but with initial-inhibit-any-policy set. The path should not
+     * validate successfully.
+     */
+    public void test4_12_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("inhibitAnyPolicy Test3 EE")
+            .withCrls("inhibitAnyPolicy1 subCA1 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA1 Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doTest();
+
+
+        new PKITSTest()
+            .withEndEntity("inhibitAnyPolicy Test3 EE")
+            .withCrls("inhibitAnyPolicy1 subCA1 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA1 Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .withInhibitAnyPolicy(true)
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.4 Invalid inhibitAnyPolicy Test4
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate asserts anyPolicy. The
+     * end entity certificate asserts anyPolicy.
+     * 66
+     */
+    public void test4_12_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitAnyPolicy Test4 EE")
+            .withCrls("inhibitAnyPolicy1 subCA1 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA1 Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.5 Invalid inhibitAnyPolicy Test5
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 5. The second intermediate certificate asserts NIST-test-policy1 and includes an inhibitAnyPolicy extension set to 1. The third intermediate certificate asserts
+     * NIST-test-policy-1 and the end entity certificate asserts anyPolicy.
+     */
+    public void test4_12_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitAnyPolicy Test5 EE")
+            .withCrls("inhibitAnyPolicy5 subsubCA CRL")
+            .withCACert("inhibitAnyPolicy5 subsubCA Cert")
+            .withCrls("inhibitAnyPolicy5 subCA CRL")
+            .withCACert("inhibitAnyPolicy5 subCA Cert")
+            .withCrls("inhibitAnyPolicy5 CA CRL")
+            .withCACert("inhibitAnyPolicy5 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.6 Invalid inhibitAnyPolicy Test6
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate asserts NIST-test-policy1 and includes an inhibitAnyPolicy extension set to 5. The end entity certificate asserts
+     * anyPolicy.
+     */
+    public void test4_12_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid inhibitAnyPolicy Test6 EE")
+            .withCrls("inhibitAnyPolicy1 subCAIAP5 CRL")
+            .withCACert("inhibitAnyPolicy1 subCAIAP5 Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.7 Valid Self-Issued inhibitAnyPolicy Test7
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate is a self-issued certificate
+     * that asserts NIST-test-policy-1. The third intermediate certificate asserts anyPolicy and the end
+     * entity certificate asserts NIST-test-policy-1.
+     */
+    public void test4_12_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid SelfIssued inhibitAnyPolicy Test7 EE")
+            .withCrls("inhibitAnyPolicy1 subCA2 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA2 Cert")
+            .withCACert("inhibitAnyPolicy1 SelfIssued CA Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.12.8 Invalid Self-Issued inhibitAnyPolicy Test8
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate is a self-issued certificate
+     * that asserts NIST-test-policy-1. The third and fourth intermediate certificates assert anyPolicy
+     * and the end entity certificate asserts NIST-test-policy-1.
+     * 68
+     */
+    public void test4_12_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitAnyPolicy Test8 EE")
+            .withCrls("inhibitAnyPolicy1 subsubCA2 CRL")
+            .withCACert("inhibitAnyPolicy1 subsubCA2 Cert")
+            .withCrls("inhibitAnyPolicy1 subCA2 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA2 Cert")
+            .withCACert("inhibitAnyPolicy1 SelfIssued CA Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doExceptionTest(1, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.12.9 Valid Self-Issued inhibitAnyPolicy Test9
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate is a self-issued certificate
+     * that asserts NIST-test-policy-1. The third intermediate certificate asserts anyPolicy. The fourth
+     * intermediate certificate is a self-issued certificate that asserts anyPolicy. The end entity certificate
+     * asserts NIST-test-policy-1.
+     */
+    public void test4_12_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withPolicyByName("NIST-test-policy-1")
+            .withEndEntity("Valid SelfIssued inhibitAnyPolicy Test9 EE")
+            .withCACert("inhibitAnyPolicy1 SelfIssued subCA2 Cert")
+            .withCrls("inhibitAnyPolicy1 subCA2 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA2 Cert")
+            .withCACert("inhibitAnyPolicy1 SelfIssued CA Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.12.10 Invalid Self-Issued inhibitAnyPolicy Test10
+     * <p>
+     * In this test, the first intermediate certificate asserts NIST-test-policy-1 and includes an
+     * inhibitAnyPolicy extension set to 1. The second intermediate certificate is a self-issued certificate
+     * that asserts NIST-test-policy-1. The third intermediate certificate asserts anyPolicy. The end
+     * entity certificate is a self-issued CA certificate that asserts anyPolicy.
+     */
+    public void test4_12_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid SelfIssued inhibitAnyPolicy Test10 EE")
+            .withCrls("inhibitAnyPolicy1 subCA2 CRL")
+            .withCACert("inhibitAnyPolicy1 subCA2 Cert")
+            .withCACert("inhibitAnyPolicy1 SelfIssued CA Cert")
+            .withCrls("inhibitAnyPolicy1 CA CRL")
+            .withCACert("inhibitAnyPolicy1 CA Cert")
+            .doExceptionTest(0, "No valid policy tree found when one expected.");
+    }
+
+    /**
+     * 4.13.1 Valid DN nameConstraints Test1
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subject name that falls within that
+     * subtree.
+     * 70
+     */
+    public void test4_13_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test1 EE")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.2 Invalid DN nameConstraints Test2
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subject name that falls outside that
+     * subtree.
+     */
+    public void test4_13_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test2 EE")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.3 Invalid DN nameConstraints Test3
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subject name that falls within that
+     * subtree and a subjectAltName extension with a DN that falls outside the subtree.
+     */
+    public void test4_13_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test3 EE")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.4 Valid DN nameConstraints Test4
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subject name that falls within that
+     * subtree and a subjectAltName extension with an e-mail address.
+     * 71
+     */
+    public void test4_13_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test4 EE")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.5 Valid DN nameConstraints Test5
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies two
+     * permitted subtrees. The end entity certificate includes a subject name that falls within one of the
+     * subtrees and a subjectAltName extension with a DN that falls within the other subtree.
+     */
+    public void test4_13_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test5 EE")
+            .withCrls("nameConstraints DN2 CA CRL")
+            .withCACert("nameConstraints DN2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.6 Valid DN nameConstraints Test6
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subject name that falls outside that
+     * subtree.
+     */
+    public void test4_13_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test6 EE")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.7 Invalid DN nameConstraints Test7
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subject name that falls within that
+     * subtree.
+     * 72
+     */
+    public void test4_13_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test7 EE")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.8 Invalid DN nameConstraints Test8
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies two
+     * excluded subtrees. The end entity certificate includes a subject name that falls within the first
+     * subtree.
+     */
+    public void test4_13_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test8 EE")
+            .withCrls("nameConstraints DN4 CA CRL")
+            .withCACert("nameConstraints DN4 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.9 Invalid DN nameConstraints Test9
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies two
+     * excluded subtrees. The end entity certificate includes a subject name that falls within the second
+     * subtree.
+     */
+    public void test4_13_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test9 EE")
+            .withCrls("nameConstraints DN4 CA CRL")
+            .withCACert("nameConstraints DN4 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.10 Invalid DN nameConstraints Test10
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * permitted subtree and an excluded subtree. The excluded subtree specifies a subset of the name
+     * space specified by the permitted subtree. The end entity certificate includes a subject name that
+     * falls within both the permitted and excluded subtrees.
+     * 73
+     */
+    public void test4_13_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test10 EE")
+            .withCrls("nameConstraints DN5 CA CRL")
+            .withCACert("nameConstraints DN5 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.11 Valid DN nameConstraints Test11
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * permitted subtree and an excluded subtree. The excluded subtree specifies a subset of the name
+     * space specified by the permitted subtree. The end entity certificate includes a subject name that
+     * falls within the permitted subtree but falls outside the excluded subtree.
+     */
+    public void test4_13_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test11 EE")
+            .withCrls("nameConstraints DN5 CA CRL")
+            .withCACert("nameConstraints DN5 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.12 Invalid DN nameConstraints Test12
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The second intermediate certificate includes a subject name that falls
+     * within that subtree and a nameConstraints extension that specifies a permitted subtree that is a
+     * subtree of the constraint specified in the first intermediate certificate. The end entity certificate
+     * includes a subject name that falls within the subtree specified by the first intermediate certificate
+     * but outside the subtree specified by the second intermediate certificate.
+     */
+    public void test4_13_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test12 EE")
+            .withCrls("nameConstraints DN1 subCA1 CRL")
+            .withCACert("nameConstraints DN1 subCA1 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.13 Invalid DN nameConstraints Test13
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The second intermediate certificate includes a subject name that falls
+     * within that subtree and a nameConstraints extension that specifies a permitted subtree that does
+     * not overlap with the permitted subtree specified in the first intermediate certificate. The end entity
+     * certificate includes a subject name that falls within the subtree specified by the first intermediate
+     * certificate.
+     */
+    public void test4_13_13()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test13 EE")
+            .withCrls("nameConstraints DN1 subCA2 CRL")
+            .withCACert("nameConstraints DN1 subCA2 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.14 Valid DN nameConstraints Test14
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The second intermediate certificate includes a subject name that falls
+     * within that subtree and a nameConstraints extension that specifies a permitted subtree that does
+     * not overlap with the permitted subtree specified in the first intermediate certificate. The end entity
+     * certificate has a null subject name (i.e., the subject name is a sequence of zero relative
+     * distinguished names) and a critical subjectAltName extension with an e-mail address.
+     */
+    public void test4_13_14()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test14 EE")
+            .withCrls("nameConstraints DN1 subCA2 CRL")
+            .withCACert("nameConstraints DN1 subCA2 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.15 Invalid DN nameConstraints Test15
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The second intermediate certificate has a subject name that falls outside
+     * that subtree and includes a nameConstraints extension that specifies an excluded subtree that
+     * does not overlap with the subtree specified in the first intermediate certificate. The end entity
+     * certificate includes a subject name that falls within the subtree specified in the first intermediate
+     * certificate.
+     */
+    public void test4_13_15()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test15 EE")
+            .withCrls("nameConstraints DN3 subCA1 CRL")
+            .withCACert("nameConstraints DN3 subCA1 Cert")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doExceptionTest(00, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.16 Invalid DN nameConstraints Test16
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The second intermediate certificate has a subject name that falls outside
+     * that subtree and includes a nameConstraints extension that specifies an excluded subtree that
+     * does not overlap with the subtree specified in the first intermediate certificate. The end entity
+     * certificate includes a subject name that falls within the subtree specified in the second intermediate
+     * certificate.
+     */
+    public void test4_13_16()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test16 EE")
+            .withCrls("nameConstraints DN3 subCA1 CRL")
+            .withCACert("nameConstraints DN3 subCA1 Cert")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.17 Invalid DN nameConstraints Test17
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The second intermediate certificate has a subject name that falls outside
+     * that subtree and includes a nameConstraints extension that specifies a permitted subtree that is a
+     * superset of the subtree specified in the first intermediate certificate. The end entity certificate
+     * includes a subject name that falls within the excluded subtree specified in the first intermediate
+     * certificate.
+     */
+    public void test4_13_17()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test17 EE")
+            .withCrls("nameConstraints DN3 subCA2 CRL")
+            .withCACert("nameConstraints DN3 subCA2 Cert")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.18 Valid DN nameConstraints Test18
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The second intermediate certificate has a subject name that falls outside
+     * that subtree and includes a nameConstraints extension that specifies a permitted subtree that is a
+     * superset of the subtree specified in the first intermediate certificate. The end entity certificate
+     * includes a subject name that falls within the permitted subtree specified in the second intermediate
+     * certificate but outside the excluded subtree specified in the first intermediate certificate.
+     */
+    public void test4_13_18()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test18 EE")
+            .withCrls("nameConstraints DN3 subCA2 CRL")
+            .withCACert("nameConstraints DN3 subCA2 Cert")
+            .withCrls("nameConstraints DN3 CA CRL")
+            .withCACert("nameConstraints DN3 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.19 Valid Self-Issued DN nameConstraints Test19
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The second intermediate certificate is a self-issued certificate. The
+     * subject name in the self-issued certificate does not fall within the permitted subtree specified in the
+     * first intermediate certificate. The end entity certificate includes a subject name that falls within the
+     * permitted subtree specified in the first intermediate certificate.
+     */
+    public void test4_13_19()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN nameConstraints Test19 EE")
+            .withCACert("nameConstraints DN1 SelfIssued CA Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.20 Invalid Self-Issued DN nameConstraints Test20
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate is a self-issued certificate. The subject name in
+     * the self-issued certificate does not fall within the permitted subtree specified in the intermediate
+     * certificate.
+     */
+    public void test4_13_20()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN nameConstraints Test20 EE")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject failed.");
+    }
+
+    /**
+     * 4.13.21 Valid RFC822 nameConstraints Test21
+     * <p>
+     * �
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls within that subtree.
+     */
+    public void test4_13_21()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid RFC822 nameConstraints Test21 EE")
+            .withCrls("nameConstraints RFC822 CA1 CRL")
+            .withCACert("nameConstraints RFC822 CA1 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.22 Invalid RFC822 nameConstraints Test22
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls outside that subtree.
+     */
+    public void test4_13_22()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid RFC822 nameConstraints Test22 EE")
+            .withCrls("nameConstraints RFC822 CA1 CRL")
+            .withCACert("nameConstraints RFC822 CA1 Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.23 Valid RFC822 nameConstraints Test23
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls within that subtree.
+     */
+    public void test4_13_23()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid RFC822 nameConstraints Test23 EE")
+            .withCrls("nameConstraints RFC822 CA2 CRL")
+            .withCACert("nameConstraints RFC822 CA2 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.24 Invalid RFC822 nameConstraints Test24
+     * <p>
+     * �
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls outside that subtree.
+     */
+    public void test4_13_24()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid RFC822 nameConstraints Test24 EE")
+            .withCrls("nameConstraints RFC822 CA2 CRL")
+            .withCACert("nameConstraints RFC822 CA2 Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.25 Valid RFC822 nameConstraints Test25
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls outside that subtree.
+     */
+    public void test4_13_25()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid RFC822 nameConstraints Test25 EE")
+            .withCrls("nameConstraints RFC822 CA3 CRL")
+            .withCACert("nameConstraints RFC822 CA3 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.26 Invalid RFC822 nameConstraints Test26
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with an
+     * e-mail address that falls within that subtree.
+     */
+    public void test4_13_26()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid RFC822 nameConstraints Test26 EE")
+            .withCrls("nameConstraints RFC822 CA3 CRL")
+            .withCACert("nameConstraints RFC822 CA3 Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.27 Valid DN and RFC822 nameConstraints Test27
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree of type directoryName. The second intermediate certificate includes a
+     * subject name that falls within that subtree and a nameConstraints extension that specifies a
+     * permitted subtree of type rfc822Name. The end entity certificate includes a subject name that falls
+     * within the subtree specified by the first intermediate certificate and an e-mail address that falls
+     * within the permitted subtree specified by the second intermediate certificate.
+     */
+    public void test4_13_27()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DN and RFC822 nameConstraints Test27 EE")
+            .withCrls("nameConstraints DN1 subCA3 CRL")
+            .withCACert("nameConstraints DN1 subCA3 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.28 Invalid DN and RFC822 nameConstraints Test28
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree of type directoryName. The second intermediate certificate includes a
+     * subject name that falls within that subtree and a nameConstraints extension that specifies a
+     * permitted subtree of type rfc822Name. The end entity certificate includes a subject name that falls
+     * within the subtree specified by the first intermediate certificate and an e-mail address that falls
+     * outside the permitted subtree specified by the second intermediate certificate.
+     */
+    public void test4_13_28()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN and RFC822 nameConstraints Test28 EE")
+            .withCrls("nameConstraints DN1 subCA3 CRL")
+            .withCACert("nameConstraints DN1 subCA3 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.29 Invalid DN and RFC822 nameConstraints Test29
+     * <p>
+     * In this test, the first intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree of type directoryName. The second intermediate certificate includes a
+     * subject name that falls within that subtree and a nameConstraints extension that specifies a
+     * permitted subtree of type rfc822Name. The end entity certificate includes a subject name that falls
+     * within the subtree specified by the first intermediate certificate but the subject name includes an
+     * attribute of type EmailAddress whose value falls outside the permitted subtree specified in the
+     * second intermediate certificate.
+     */
+    public void test4_13_29()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DN and RFC822 nameConstraints Test29 EE")
+            .withCrls("nameConstraints DN1 subCA3 CRL")
+            .withCACert("nameConstraints DN1 subCA3 Cert")
+            .withCrls("nameConstraints DN1 CA CRL")
+            .withCACert("nameConstraints DN1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative email failed.");
+    }
+
+    /**
+     * 4.13.30 Valid DNS nameConstraints Test30
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with a
+     * dNSName that falls within that subtree.
+     */
+    public void test4_13_30()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DNS nameConstraints Test30 EE")
+            .withCrls("nameConstraints DNS1 CA CRL")
+            .withCACert("nameConstraints DNS1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.31 Invalid DNS nameConstraints Test31
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with a
+     * dNSName that falls outside that subtree.
+     */
+    public void test4_13_31()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DNS nameConstraints Test31 EE")
+            .withCrls("nameConstraints DNS1 CA CRL")
+            .withCACert("nameConstraints DNS1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.32 Valid DNS nameConstraints Test32
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with a
+     * dNSName that falls outside that subtree.
+     */
+    public void test4_13_32()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid DNS nameConstraints Test32 EE")
+            .withCrls("nameConstraints DNS2 CA CRL")
+            .withCACert("nameConstraints DNS2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.33 Invalid DNS nameConstraints Test33
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with a
+     * dNSName that falls within that subtree.
+     */
+    public void test4_13_33()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DNS nameConstraints Test33 EE")
+            .withCrls("nameConstraints DNS2 CA CRL")
+            .withCACert("nameConstraints DNS2 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.34 Valid URI nameConstraints Test34
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with a
+     * uniformResourceIdentifier that falls within that subtree.
+     */
+    public void test4_13_34()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid URI nameConstraints Test34 EE")
+            .withCrls("nameConstraints URI1 CA CRL")
+            .withCACert("nameConstraints URI1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.35 Invalid URI nameConstraints Test35
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with a
+     * uniformResourceIdentifier that falls outside that subtree.
+     */
+    public void test4_13_35()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid URI nameConstraints Test35 EE")
+            .withCrls("nameConstraints URI1 CA CRL")
+            .withCACert("nameConstraints URI1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.36 Valid URI nameConstraints Test36
+     * <p>
+     * �
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with a
+     * uniformResourceIdentifier that falls outside that subtree.
+     */
+    public void test4_13_36()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid URI nameConstraints Test36 EE")
+            .withCrls("nameConstraints URI2 CA CRL")
+            .withCACert("nameConstraints URI2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.13.37 Invalid URI nameConstraints Test37
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single excluded subtree. The end entity certificate includes a subjectAltName extension with a
+     * uniformResourceIdentifier that falls within that subtree.
+     */
+    public void test4_13_37()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid URI nameConstraints Test37 EE")
+            .withCrls("nameConstraints URI2 CA CRL")
+            .withCACert("nameConstraints URI2 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.13.38 Invalid DNS nameConstraints Test38
+     * <p>
+     * In this test, the intermediate certificate includes a nameConstraints extension that specifies a
+     * single permitted subtree. The end entity certificate includes a subjectAltName extension with a
+     * dNSName that falls outside that subtree. The permitted subtree is “testcertificates.gov” and the
+     * subjectAltName is “mytestcertificates.gov”.
+     */
+    public void test4_13_38()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid DNS nameConstraints Test38 EE")
+            .withCrls("nameConstraints DNS1 CA CRL")
+            .withCACert("nameConstraints DNS1 CA Cert")
+            .doExceptionTest(0, "Subtree check for certificate subject alternative name failed.");
+    }
+
+    /**
+     * 4.14.1 Valid distributionPoint Test1
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint.
+     */
+    public void test4_14_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid distributionPoint Test1 EE")
+            .withCrls("distributionPoint1 CA CRL")
+            .withCACert("distributionPoint1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.2 Invalid distributionPoint Test2
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint. The CRL lists the end entity certificate as being revoked.
+     */
+    public void test4_14_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid distributionPoint Test2 EE")
+            .withCrls("distributionPoint1 CA CRL")
+            .withCACert("distributionPoint1 CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.3 Invalid distributionPoint Test3
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The only CRL
+     * available from the issuer of the end entity certificate includes an issuingDistributionPoint
+     * extension with a distributionPoint that does not match the distributionPoint specified in the end
+     * entity certificate.
+     */
+    public void test4_14_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid distributionPoint Test3 EE")
+            .withCrls("distributionPoint1 CA CRL")
+            .withCACert("distributionPoint1 CA Cert")
+            .doExceptionTest(0, "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    /**
+     * 4.14.4 Valid distributionPoint Test4
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint. The distributionPoint in the end entity certificate is specified as a
+     * nameRelativeToCRLIssuer while the distributionPoint in the CRL is specified as a fullName.
+     */
+    public void test4_14_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid distributionPoint Test4 EE")
+            .withCrls("distributionPoint1 CA CRL")
+            .withCACert("distributionPoint1 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.5 Valid distributionPoint Test5
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint. The distributionPoint in both the end entity certificate and the CRL are
+     * specified as a nameRelativeToCRLIssuer.
+     * 85
+     */
+    public void test4_14_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid distributionPoint Test5 EE")
+            .withCrls("distributionPoint2 CA CRL")
+            .withCACert("distributionPoint2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.6 Invalid distributionPoint Test6
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint. The distributionPoint in both the end entity certificate and the CRL are
+     * specified as a nameRelativeToCRLIssuer. The CRL lists the end entity certificate as being
+     * revoked.
+     */
+    public void test4_14_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid distributionPoint Test6 EE")
+            .withCrls("distributionPoint2 CA CRL")
+            .withCACert("distributionPoint2 CA Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.7 Valid distributionPoint Test7
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a matching
+     * distributionPoint. The distributionPoint in the CRL is specified as a
+     * nameRelativeToCRLIssuer and the distributionPoint in the end entity certificate is specified as
+     * a fullName.
+     */
+    public void test4_14_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid distributionPoint Test7 EE")
+            .withCrls("distributionPoint2 CA CRL")
+            .withCACert("distributionPoint2 CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.8 Invalid distributionPoint Test8
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a single
+     * DistributionPoint consisting of a distributionPoint with a distinguished name. The CRL that
+     * covers the end entity certificate includes an issuingDistributionPoint extension with a
+     * distributionPoint that does not match. The distributionPoint in the CRL is specified as a
+     * nameRelativeToCRLIssuer and the distributionPoint in the end entity certificate is specified as
+     * a fullName.
+     */
+    public void test4_14_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid distributionPoint Test8 EE")
+            .withCrls("distributionPoint2 CA CRL")
+            .withCACert("distributionPoint2 CA Cert")
+            .doExceptionTest(0, "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    /**
+     * 4.14.9 Invalid distributionPoint Test9
+     * <p>
+     * In this test, the CRL that covers the end entity certificate includes an issuingDistributionPoint
+     * extension with a distributionPoint. The distributionPoint does not match the CRL issuer's
+     * name. The end entity certificate does not include a cRLDistributionPoints extension
+     */
+    public void test4_14_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid distributionPoint Test9 EE")
+            .withCrls("distributionPoint2 CA CRL")
+            .withCACert("distributionPoint2 CA Cert")
+            .doExceptionTest(0, "No match for certificate CRL issuing distribution point name to cRLIssuer CRL distribution point.");
+    }
+
+    /**
+     * 4.14.10 Valid No issuingDistributionPoint Test10
+     * <p>
+     * In this test, the CRL that covers the end entity certificate does not include an
+     * issuingDistributionPoint extension. The end entity certificate includes a
+     * cRLDistributionPoints extension with a distributionPoint name.
+     */
+    public void test4_14_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid No issuingDistributionPoint Test10 EE")
+            .withCrls("No issuingDistributionPoint CA CRL")
+            .withCACert("No issuingDistributionPoint CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.11 Invalid onlyContainsUserCerts CRL Test11
+     * <p>
+     * In this test, the only CRL issued by the intermediate CA includes an issuingDistributionPoint
+     * extension with onlyContainsUserCerts set to TRUE. The final certificate in the path is a CA
+     * certificate.
+     */
+    public void test4_14_11()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlyContainsUserCerts Test11 EE")
+            .withCrls("onlyContainsUserCerts CA CRL")
+            .withCACert("onlyContainsUserCerts CA Cert")
+            .doExceptionTest(0, "CA Cert CRL only contains user certificates.");
+    }
+
+    /**
+     * 4.14.12 Invalid onlyContainsCACerts CRL Test12
+     * <p>
+     * In this test, the only CRL issued by the intermediate CA includes an issuingDistributionPoint
+     * extension with onlyContainsCACerts set to TRUE.
+     */
+    public void test4_14_12()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlyContainsCACerts Test12 EE")
+            .withCrls("onlyContainsCACerts CA CRL")
+            .withCACert("onlyContainsCACerts CA Cert")
+            .doExceptionTest(0, "End CRL only contains CA certificates.");
+    }
+
+    /**
+     * 4.14.13 Valid onlyContainsCACerts CRL Test13
+     * <p>
+     * In this test, the only CRL issued by the intermediate CA includes an issuingDistributionPoint
+     * extension with onlyContainsCACerts set to TRUE. The final certificate in the path is a CA
+     * certificate.
+     */
+    public void test4_14_13()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid onlyContainsCACerts Test13 EE")
+            .withCrls("onlyContainsCACerts CA CRL")
+            .withCACert("onlyContainsCACerts CA Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.14 Invalid onlyContainsAttributeCerts Test14
+     * <p>
+     * In this test, the only CRL issued by the intermediate CA includes an issuingDistributionPoint
+     * extension with onlyContainsAttributeCerts set to TRUE.
+     */
+    public void test4_14_14()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlyContainsAttributeCerts Test14 EE")
+            .withCrls("onlyContainsAttributeCerts CA CRL")
+            .withCACert("onlyContainsAttributeCerts CA Cert")
+            .doExceptionTest(0, "onlyContainsAttributeCerts boolean is asserted.");
+    }
+
+    /**
+     * 4.14.15 Invalid onlySomeReasons Test15
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. The end
+     * entity certificate has been revoked for key compromise.
+     */
+    public void test4_14_15()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlySomeReasons Test15 EE")
+            .withCrls("onlySomeReasons CA1 other reasons CRL")
+            .withCrls("onlySomeReasons CA1 compromise CRL")
+            .withCACert("onlySomeReasons CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.16 Invalid onlySomeReasons Test16
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. The end
+     * entity certificate has been placed on hold.
+     */
+    public void test4_14_16()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlySomeReasons Test16 EE")
+            .withCrls("onlySomeReasons CA1 other reasons CRL")
+            .withCrls("onlySomeReasons CA1 compromise CRL")
+            .withCACert("onlySomeReasons CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: certificateHold");
+    }
+
+    /**
+     * 4.14.17 Invalid onlySomeReasons Test17
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the affiliationChanged
+     * and superseded reason codes and the other covering the cessationOfOperation and
+     * certificateHold reason codes. The end entity certificate is not listed on either CRL.
+     */
+    public void test4_14_17()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlySomeReasons Test17 EE")
+            .withCrls("onlySomeReasonsCA2 CRL2")
+            .withCrls("onlySomeReasons CA2 CRL1")
+            .withCACert("onlySomeReasons CA2 Cert")
+            .doExceptionTest(0, "Certificate status could not be determined.");
+    }
+
+    /**
+     * 4.14.18 Valid onlySomeReasons Test18
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. Both CRLs
+     * include an issuingDistributionPoint extension with the same distributionPoint name. The end
+     * entity certificate includes a cRLDistributionPoints extension with the same distributionPoint
+     * name.
+     */
+    public void test4_14_18()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid onlySomeReasons Test18 EE")
+            .withCrls("onlySomeReasons CA3 other reasons CRL")
+            .withCrls("onlySomeReasons CA3 compromise CRL")
+            .withCACert("onlySomeReasons CA3 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.19 Valid onlySomeReasons Test19
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. Both CRLs
+     * include an issuingDistributionPoint extension with a different distributionPoint name. The end
+     * entity certificate includes a cRLDistributionPoints extension with two DistributionPoints, one
+     * for each CRL.
+     */
+    public void test4_14_19()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid onlySomeReasons Test19 EE")
+            .withCrls("onlySomeReasons CA4 other reasons CRL")
+            .withCrls("onlySomeReasons CA4 compromise CRL")
+            .withCACert("onlySomeReasons CA4 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.20 Invalid onlySomeReasons Test20
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. Both CRLs
+     * include an issuingDistributionPoint extension with a different distributionPoint name. The end
+     * entity certificate includes a cRLDistributionPoints extension with two DistributionPoints, one
+     * for each CRL. The end entity certificate has been revoked for key compromise.
+     */
+    public void test4_14_20()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlySomeReasons Test20 EE")
+            .withCrls("onlySomeReasons CA4 other reasons CRL")
+            .withCrls("onlySomeReasons CA4 compromise CRL")
+            .withCACert("onlySomeReasons CA4 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.21 Invalid onlySomeReasons Test21
+     * <p>
+     * In this test, the intermediate certificate has issued two CRLs, one covering the keyCompromise
+     * and cACompromise reason codes and the other covering the remaining reason codes. Both CRLs
+     * include an issuingDistributionPoint extension with a different distributionPoint name. The end
+     * entity certificate includes a cRLDistributionPoints extension with two DistributionPoints, one
+     * for each CRL. The end entity certificate has been revoked as a result of a change in affiliation.
+     */
+    public void test4_14_21()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid onlySomeReasons Test21 EE")
+            .withCrls("onlySomeReasons CA4 other reasons CRL")
+            .withCrls("onlySomeReasons CA4 compromise CRL")
+            .withCACert("onlySomeReasons CA4 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: affiliationChanged");
+    }
+
+    /**
+     * 4.14.22 Valid IDP with indirectCRL Test22
+     * <p>
+     * In this test, the intermediate CA has issued a CRL that contains an issuingDistributionPoint
+     * extension with the indirectCRL flag set. The end entity certificate was issued by the intermediate
+     * CA.
+     * 91
+     */
+    public void test4_14_22()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid IDP with indirectCRL Test22 EE")
+            .withCrls("indirectCRL CA1 CRL")
+            .withCACert("indirectCRL CA1 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.23 Invalid IDP with indirectCRL Test23
+     * <p>
+     * In this test, the intermediate CA has issued a CRL that contains an issuingDistributionPoint
+     * extension with the indirectCRL flag set. The end entity certificate was issued by the intermediate
+     * CA and is listed as revoked on the CRL.
+     */
+    public void test4_14_23()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid IDP with indirectCRL Test23 EE")
+            .withCrls("indirectCRL CA1 CRL")
+            .withCACert("indirectCRL CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.24 Valid IDP with indirectCRL Test24
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The public key needed to validate the indirect CRL is in a certificate issued by the Trust Anchor.
+     */
+    // CHECK.
+    public void xtest4_14_24()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid IDP with indirectCRL Test24 EE")
+            .withCrls("indirectCRL CA1 CRL")
+            .withCACert("indirectCRL CA1 Cert")
+            .withCACert("indirectCRL CA2 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.25 Valid IDP with indirectCRL Test25
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The public key needed to validate the indirect CRL is in a certificate issued by the Trust Anchor.
+     * The end entity's serial number is listed on the CRL, but there is no certificateIssuer CRL entry
+     * extension, indicating that the revoked certificate was one issued by the CRL issuer.
+     * 92
+     */
+    public void xtest4_14_25()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid IDP with indirectCRL Test25 EE")
+            .withCrls("indirectCRL CA1 CRL")
+            .withCACert("indirectCRL CA1 Cert")
+            .withCACert("indirectCRL CA2 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.26 Invalid IDP with indirectCRL Test26
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The entity specified in the cRLIssuer field does not exist.
+     */
+    // CHECK not forming path, "Trust anchor for certification path not found."
+    // Expected it to be failing because the end entity has been revoked.
+    public void xtest4_14_26()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid IDP with indirectCRL Test26 EE")
+            .withCrls("indirectCRL CA1 CRL")
+            .withCACert("indirectCRL CA1 Cert")
+            .withCACert("indirectCRL CA2 Cert")
+            .doExceptionTest(-1, "--");
+    }
+
+    /**
+     * 4.14.27 Invalid cRLIssuer Test27
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The CRL issued by the entity specified in the cRLIssuer field does not include an
+     * issuingDistributionPoint extension.
+     */
+    // CHECK not forming path, "Trust anchor for certification path not found."
+    public void xtest4_14_27()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cRLIssuer Test27 EE")
+            .withCrls("Good CA CRL")
+            .withCACert("Good CA Cert")
+            .withCACert("indirectCRL CA2 Cert")
+            .doExceptionTest(-1, "--");
+    }
+
+    /**
+     * 4.14.28 Valid cRLIssuer Test28
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * <p>
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The indirect CRL issuer has been issued a certificate by the issuer of the end entity certificate. The
+     * certificate issued to the CRL issuer is covered by a CRL issued by the issuer of the end entity
+     * certificate.
+     */
+    public void xtest4_14_28()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid cRLIssuer Test28 EE")
+            .withCrls("indirectCRL CA3 cRLIssuer CRL")
+            .withCACert("indirectCRL CA3 cRLIssuer Cert")
+            .withCrls("indirectCRL CA3 CRL")
+            .withCACert("indirectCRL CA3 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.29 Valid cRLIssuer Test29
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The distributionPoint in the end entity certificate is specified as nameRelativeToCRLIssuer.
+     * The indirect CRL issuer has been issued a certificate by the issuer of the end entity certificate. The
+     * certificate issued to the CRL issuer is covered by a CRL issued by the issuer of the end entity
+     * certificate.
+     */
+    public void xtest4_14_29()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid cRLIssuer Test29 EE")
+            .withCrls("indirectCRL CA3 cRLIssuer CRL")
+            .withCACert("indirectCRL CA3 cRLIssuer Cert")
+            .withCrls("indirectCRL CA3 CRL")
+            .withCACert("indirectCRL CA3 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.30 Valid cRLIssuer Test30
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The indirect CRL issuer has been issued a certificate by the issuer of the end entity certificate.
+     * Both the end entity certificate and the certificate issued to the CRL issuer are covered by the
+     * indirect CRL issued by the CRL issuer.
+     */
+    public void xtest4_14_30()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid cRLIssuer Test30 EE")
+            .withCrls("indirectCRL CA4 cRLIssuer CRL")
+            .withCACert("indirectCRL CA4 cRLIssuer Cert")
+            .withCACert("indirectCRL CA4 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.31 Invalid cRLIssuer Test31
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The indirect CRL contains a CRL entry listing the end entity certificate's serial number that
+     * includes a certificateIssuer extension specifying the end entity certificate's issuer.
+     */
+    // CHECK not forming path, "Trust anchor for certification path not found."
+    // Expected it to be failing because the end entity has been revoked.
+    public void xtest4_14_31()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cRLIssuer Test31 EE")
+            .withCACert("indirectCRL CA6 Cert")
+            .withCrls("indirectCRL CA5 CRL")
+            .withCACert("indirectCRL CA5 Cert")
+            .doExceptionTest(-1, "--");
+    }
+
+    /**
+     * 4.14.32 Invalid cRLIssuer Test32
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The indirect CRL contains a CRL entry listing the end entity certificate's serial number and the
+     * preceding CRL entry includes a certificateIssuer extension specifying the end entity certificate's
+     * issuer.
+     */
+    // CHECK not forming path, "Trust anchor for certification path not found."
+    // Expected it to be failing because the end entity has been revoked.
+    public void xtest4_14_32()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cRLIssuer Test32 EE")
+            .withCACert("indirectCRL CA6 Cert")
+            .withCrls("indirectCRL CA5 CRL")
+            .withCACert("indirectCRL CA5 Cert")
+            .doExceptionTest(-1, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.33 Valid cRLIssuer Test33
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with a
+     * cRLIssuer field indicating that the CRL is issued by an entity other than the certificate issuer.
+     * The indirect CRL contains a CRL entry listing the end entity certificate's serial number, but the
+     * most recent CRL entry to include a certificateIssuer extension specified a different certificate
+     * issuer.
+     */
+    public void xtest4_14_33()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid cRLIssuer Test33 EE")
+            .withCACert("indirectCRL CA6 Cert")
+            .withCrls("indirectCRL CA5 CRL")
+            .withCACert("indirectCRL CA5 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.14.34 Invalid cRLIssuer Test34
+     * <p>
+     * In this test, the end entity certificate is issued by the same CA that issues the corresponding CRL,
+     * but the CRL is also an indirect CRL for other CAs. The end entity certificate's serial number is
+     * listed on the CRL and the most recent CRL entry to include a certificateIssuer extension specifies
+     * the end entity certificate's issuer.
+     */
+    public void test4_14_34()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cRLIssuer Test34 EE")
+            .withCrls("indirectCRL CA5 CRL")
+            .withCACert("indirectCRL CA5 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.14.35 Invalid cRLIssuer Test35
+     * <p>
+     * In this test, the end entity certificate includes a cRLDistributionPoints extension with both a
+     * distributionPoint name and a cRLIssuer field indicating that the CRL is issued by an entity other
+     * than the certificate issuer. There is no CRL available from the entity specified in cRLIssuer, but
+     * the certificate issuer has issued a CRL with an issuingDistributionPoint extension that includes a
+     * distributionPoint that matches the distributionPoint in the certificate.
+     */
+    public void test4_14_35()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid cRLIssuer Test35 EE")
+            .withCrls("indirectCRL CA5 CRL")
+            .withCACert("indirectCRL CA5 Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"ou=indirectCRL CA5,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.15.1 Invalid deltaCRLIndicator No Base Test1
+     * <p>
+     * In this test, the CRL covering the end entity certificate includes a deltaCRLIndicator extension,
+     * but no other CRLs are available for the intermediate certificate.
+     */
+    public void test4_15_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid deltaCRLIndicator No Base Test1 EE")
+            .withCrls("deltaCRLIndicator No Base CA CRL")
+            .withCACert("deltaCRLIndicator No Base CA Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=deltaCRLIndicator No Base CA,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.15.2 Valid delta-CRL Test2
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL.
+     */
+    public void test4_15_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid deltaCRL Test2 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.15.3 Invalid delta-CRL Test3
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL. The end entity certificate is listed as revoked on the
+     * complete CRL.
+     * 97
+     */
+    public void test4_15_3()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid deltaCRL Test3 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.15.4 Invalid delta-CRL Test4
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL. The end entity certificate is listed as revoked on the
+     * delta-CRL.
+     */
+    public void test4_15_4()
+        throws Exception
+    {
+        new PKITSTest()
+            .enableDeltaCRLs(true)
+            .withEndEntity("Invalid deltaCRL Test4 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.15.5 Valid delta-CRL Test5
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL. The end entity certificate is listed as on hold on the
+     * complete CRL, but the delta-CRL indicates that it should be removed from the CRL.
+     */
+    public void test4_15_5()
+        throws Exception
+    {
+        new PKITSTest()
+            .enableDeltaCRLs(true)
+            .withEndEntity("Valid deltaCRL Test5 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.15.6 Invalid delta-CRL Test6
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL. The end entity certificate is listed as on hold on the
+     * complete CRL and the delta-CRL indicates that it has been revoked.
+     */
+    public void test4_15_6()
+        throws Exception
+    {
+        new PKITSTest()
+            .enableDeltaCRLs(true)
+            .withEndEntity("Invalid deltaCRL Test6 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.15.7 Valid delta-CRL Test7
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to the complete CRL as its base CRL. The end entity certificate is not listed on the complete
+     * CRL and is listed on the delta-CRL as removeFromCRL.
+     */
+    public void test4_15_7()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid deltaCRL Test7 EE")
+            .withCrls("deltaCRL CA1 deltaCRL")
+            .withCrls("deltaCRL CA1 CRL")
+            .withCACert("deltaCRL CA1 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.15.8 Valid delta-CRL Test8
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to a CRL that was issued earlier than the complete CRL as its base CRL. The end entity
+     * certificate is not listed on either the complete CRL or the delta-CRL.
+     */
+    public void test4_15_8()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid deltaCRL Test8 EE")
+            .withCrls("deltaCRL CA2 deltaCRL")
+            .withCrls("deltaCRL CA2 CRL")
+            .withCACert("deltaCRL CA2 Cert")
+            .doTest();
+    }
+
+    /**
+     * 4.15.9 Invalid delta-CRL Test9
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to a CRL that was issued earlier than the complete CRL as its base CRL. The end entity
+     * certificate is listed as revoked on both the complete CRL and the delta-CRL.
+     */
+    public void test4_15_9()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid deltaCRL Test9 EE")
+            .withCrls("deltaCRL CA2 deltaCRL")
+            .withCrls("deltaCRL CA2 CRL")
+            .withCACert("deltaCRL CA2 Cert")
+            .doExceptionTest(0, "Certificate revocation after 2001-04-19 14:57:20 +0000, reason: keyCompromise");
+    }
+
+    /**
+     * 4.15.10 Invalid delta-CRL Test10
+     * <p>
+     * In this test, the intermediate CA has issued a complete CRL and a delta-CRL. The delta-CRL
+     * refers to a CRL that was issued later than the complete CRL as its base CRL. The end entity
+     * certificate is not listed as revoked on either the complete CRL or the delta-CRL, but the delta-CRL
+     * can not be used in conjunction with the provided complete CRL. The complete CRL has a
+     * nextUpdate time that is in the past.
+     */
+    public void test4_15_10()
+        throws Exception
+    {
+        new PKITSTest()
+            .enableDeltaCRLs(true)
+            .withEndEntity("Invalid deltaCRL Test10 EE")
+            .withCrls("deltaCRL CA3 deltaCRL")
+            .withCrls("deltaCRL CA3 CRL")
+            .withCACert("deltaCRL CA3 Cert")
+            .doExceptionTest(0, "No CRLs found for issuer \"cn=deltaCRL CA3,o=Test Certificates,c=US\"");
+    }
+
+    /**
+     * 4.16.1 Valid Unknown Not Critical Certificate Extension Test1
+     * <p>
+     * In this test, the end entity certificate contains a private, non-critical certificate extension.
+     */
+    public void test4_16_1()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Valid Unknown Not Critical Certificate Extension Test1 EE")
+            .doTest();
+    }
+
+    /**
+     * 4.16.2 Invalid Unknown Critical Certificate Extension Test2
+     * <p>
+     * In this test, the end entity certificate contains a private, critical certificate extension.
+     */
+    public void test4_16_2()
+        throws Exception
+    {
+        new PKITSTest()
+            .withEndEntity("Invalid Unknown Critical Certificate Extension Test2 EE")
+            .doExceptionTest(0, "Certificate has unsupported critical extension: [2.16.840.1.101.2.1.12.2]");
+    }
+
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/PKITSTest.java b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/PKITSTest.java
new file mode 100644
index 0000000..ad68370
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/jce/provider/test/nist/PKITSTest.java
@@ -0,0 +1,467 @@
+package org.bouncycastle.jce.provider.test.nist;
+
+import java.io.InputStream;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXCertPathValidatorResult;
+import java.security.cert.PKIXParameters;
+import java.security.cert.TrustAnchor;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.TBSCertificate;
+import org.bouncycastle.jcajce.PKIXExtendedParameters;
+
+/**
+ * Utility class to support PKITS testing of the Cert Path library and associated functions.
+ */
+
+class PKITSTest
+{
+    private Set trustAnchors = new HashSet();
+    private ArrayList certs = new ArrayList();
+    private ArrayList crls = new ArrayList();
+    private Set policies = new HashSet();
+
+    //
+    // Global to save reloading.
+    //
+    private static final Map certBuffer = new HashMap();
+    private static final Map crlBuffer = new HashMap();
+
+    private CertPath certPath;
+    private CertStore certStore;
+    private PKIXCertPathValidatorResult validatorResult;
+    private X509Certificate endCert;
+    private Boolean explicitPolicyRequired;
+    private Boolean inhibitAnyPolicy;
+    private Boolean policyMappingInhibited;
+    private boolean deltaCRLsEnabled;
+
+
+    private HashMap certsByName = new HashMap();
+    private HashMap crlsByName = new HashMap();
+
+
+    private static final HashMap<String, ASN1ObjectIdentifier> policiesByName = new HashMap<String, ASN1ObjectIdentifier>();
+
+    static
+    {
+        policiesByName.put("anyPolicy", new ASN1ObjectIdentifier("2.5.29.32.0"));
+        policiesByName.put("NIST-test-policy-1", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.1"));
+        policiesByName.put("NIST-test-policy-2", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.2"));
+        policiesByName.put("NIST-test-policy-3", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.3"));
+        policiesByName.put("NIST-test-policy-4", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.4"));
+        policiesByName.put("NIST-test-policy-5", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.5"));
+        policiesByName.put("NIST-test-policy-6", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.6"));
+        policiesByName.put("NIST-test-policy-7", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.7"));
+        policiesByName.put("NIST-test-policy-8", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.8"));
+        policiesByName.put("NIST-test-policy-9", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.9"));
+        policiesByName.put("NIST-test-policy-10", new ASN1ObjectIdentifier("2.16.840.1.101.3.2.1.48.10"));
+    }
+
+
+    public static ASN1ObjectIdentifier[] resolvePolicyOid(String... nistNames)
+    {
+        ASN1ObjectIdentifier[] oids = new ASN1ObjectIdentifier[nistNames.length];
+
+        int c = 0;
+        for (String name : nistNames)
+        {
+            ASN1ObjectIdentifier oid = policiesByName.get(name);
+
+            if (oid == null)
+            {
+                oid = new ASN1ObjectIdentifier(name);
+            }
+            oids[c++] = oid;
+        }
+
+        return oids;
+    }
+
+
+    public PKITSTest()
+        throws Exception
+    {
+        trustAnchors.add(getTrustAnchor("TrustAnchorRootCertificate"));
+        withCrls("TrustAnchorRootCRL");
+    }
+
+    PKITSTest enableDeltaCRLs(boolean enabled)
+    {
+        this.deltaCRLsEnabled = enabled;
+
+        return this;
+    }
+
+    PKITSTest withCrls(String... crls)
+        throws Exception
+    {
+        for (String name : crls)
+        {
+            name = name.replace(" ", "").replace("-", "");
+            this.crls.add(loadCrl(name));
+        }
+        return this;
+    }
+
+    PKITSTest withCACert(String... certs)
+    {
+        for (String name : certs)
+        {
+            name = name.replace(" ", "").replace("-", "");
+            this.certs.add(loadCert(name));
+        }
+        return this;
+    }
+
+
+    public PKITSTest withPolicyByName(String... policies)
+    {
+        withPolicyByOids(resolvePolicyOid(policies));
+        return this;
+    }
+
+
+    public PKITSTest withExplicitPolicyRequired(boolean required)
+    {
+        this.explicitPolicyRequired = required;
+        return this;
+    }
+
+    public PKITSTest withPolicyByOids(ASN1ObjectIdentifier... policies)
+    {
+
+        for (ASN1ObjectIdentifier policy : policies)
+        {
+            this.policies.add(policy.toString());
+        }
+
+        return this;
+    }
+
+
+    PKIXCertPathValidatorResult doTest()
+        throws Exception
+    {
+        List certsAndCrls = new ArrayList();
+
+        certsAndCrls.add(endCert);
+        certsAndCrls.addAll(certs);
+
+        certPath = CertificateFactory.getInstance("X.509", "BC").generateCertPath(certsAndCrls);
+
+        certsAndCrls.addAll(crls);
+
+        certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certsAndCrls), "BC");
+
+        CertPathValidator validator = CertPathValidator.getInstance("PKIX", "BC");
+        PKIXParameters params = new PKIXParameters(trustAnchors);
+
+        params.addCertStore(certStore);
+        params.setRevocationEnabled(true);
+        params.setDate(new GregorianCalendar(2010, 1, 1).getTime());
+
+        if (explicitPolicyRequired != null)
+        {
+            params.setExplicitPolicyRequired(explicitPolicyRequired);
+        }
+
+        if (inhibitAnyPolicy != null)
+        {
+            params.setAnyPolicyInhibited(inhibitAnyPolicy);
+        }
+
+        if (policyMappingInhibited != null)
+        {
+            params.setPolicyMappingInhibited(policyMappingInhibited);
+        }
+
+        if (!policies.isEmpty())
+        {
+            params.setExplicitPolicyRequired(true);
+            params.setInitialPolicies(policies);
+        }
+
+        PKIXExtendedParameters.Builder extParams = new PKIXExtendedParameters.Builder(params);
+
+        extParams.setUseDeltasEnabled(deltaCRLsEnabled);
+
+        validatorResult = (PKIXCertPathValidatorResult)validator.validate(certPath, extParams.build());
+
+        return validatorResult;
+    }
+
+    void doExceptionTest(
+        int index,
+        String message)
+        throws Exception
+    {
+        try
+        {
+            doTest();
+
+            throw new RuntimeException("path accepted when should be rejected");
+        }
+        catch (CertPathValidatorException e)
+        {
+            if (index != e.getIndex())
+            {
+                throw new RuntimeException("Index did not match: " + index + " got " + e.getIndex());
+            }
+
+            if (!message.equals(e.getMessage()))
+            {
+                throw new RuntimeException("Message did not match: '" + message + "', got '" + e.getMessage() + "'");
+            }
+        }
+    }
+
+    X509Certificate pathCert(int index)
+    {
+        List<? extends Certificate> certificates = certPath.getCertificates();
+        if (index >= certificates.size())
+        {
+            throw new IllegalArgumentException("Index " + index + "  exceeds available certificates in path, " + certificates.size());
+        }
+
+        return (X509Certificate)certificates.get(index);
+    }
+
+    TBSCertificate pathTBSCert(int index)
+        throws Exception
+    {
+        List<? extends Certificate> certificates = certPath.getCertificates();
+        if (index >= certificates.size())
+        {
+            throw new IllegalArgumentException("Index " + index + "  exceeds available certificates in path, " + certificates.size());
+        }
+
+        X509Certificate cert = (X509Certificate)certificates.get(index);
+
+
+        return TBSCertificate.getInstance(cert.getTBSCertificate());
+    }
+
+    /**
+     * Test a certificate in the path has the folling usage
+     *
+     * @param certIndex The certificate index.
+     * @param usage     An integer build from KeyUsage class constants, eg  KeyUsage.cRLSign | KeyUsage.keyCertSign
+     * @return true if all are found.
+     * @throws Exception
+     */
+    public boolean certHasKeyUsage(int certIndex, int usage)
+        throws Exception
+    {
+        KeyUsage ku = KeyUsage.fromExtensions(pathTBSCert(certIndex).getExtensions());
+
+        return ku.hasUsages(usage);
+    }
+
+    public BasicConstraints certBasicConstraints(int certIndex)
+        throws Exception
+    {
+        return BasicConstraints.fromExtensions(pathTBSCert(certIndex).getExtensions());
+    }
+
+
+    public Set getTrustAnchors()
+    {
+        return trustAnchors;
+    }
+
+    public ArrayList getCerts()
+    {
+        return certs;
+    }
+
+    public ArrayList getCrls()
+    {
+        return crls;
+    }
+
+    public Set getPolicies()
+    {
+        return policies;
+    }
+
+    public static Map getCertBuffer()
+    {
+        return certBuffer;
+    }
+
+    public static Map getCrlBuffer()
+    {
+        return crlBuffer;
+    }
+
+    public CertPath getCertPath()
+    {
+        return certPath;
+    }
+
+    public CertStore getCertStore()
+    {
+        return certStore;
+    }
+
+    public PKIXCertPathValidatorResult getValidatorResult()
+    {
+        return validatorResult;
+    }
+
+    public X509Certificate getEndCert()
+    {
+        return endCert;
+    }
+
+    private X509Certificate loadCert(
+        final String certName)
+    {
+        X509Certificate cert;
+        synchronized (certBuffer)
+        {
+            cert = (X509Certificate)certBuffer.get(certName);
+        }
+
+        if (cert != null)
+        {
+            certsByName.put(certName, cert);
+            return cert;
+        }
+
+        try
+        {
+            String path = getPkitsHome() + "/certs/" + certName + ".crt";
+            InputStream in = this.getClass().getResourceAsStream(path);
+
+            if (in == null)
+            {
+                throw new RuntimeException("Could not find: " + path);
+            }
+
+
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+
+            cert = (X509Certificate)fact.generateCertificate(in);
+
+            synchronized (certBuffer)
+            {
+                certsByName.put(certName, cert);
+                certBuffer.put(certName, cert);
+            }
+            return cert;
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException("exception loading certificate " + certName + ": " + e);
+        }
+    }
+
+    private X509CRL loadCrl(
+        String crlName)
+        throws Exception
+    {
+        X509CRL crl;
+        synchronized (crlBuffer)
+        {
+            crl = (X509CRL)crlBuffer.get(crlName);
+        }
+        if (crl != null)
+        {
+            crlsByName.put(crlName, crl);
+            return crl;
+        }
+
+        try
+        {
+            InputStream in = this.getClass().getResourceAsStream(getPkitsHome() + "/crls/" + crlName + ".crl");
+
+            CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC");
+
+            crl = (X509CRL)fact.generateCRL(in);
+
+            synchronized (crlBuffer)
+            {
+                crlsByName.put(crlName, crl);
+                crlBuffer.put(crlName, crl);
+            }
+
+
+            return crl;
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException("exception loading CRL: " + crlName);
+        }
+    }
+
+    private TrustAnchor getTrustAnchor(String trustAnchorName)
+        throws Exception
+    {
+        X509Certificate cert = loadCert(trustAnchorName);
+        byte[] extBytes = cert.getExtensionValue(Extension.nameConstraints.getId());
+
+        if (extBytes != null)
+        {
+            ASN1Encodable extValue = ASN1Primitive.fromByteArray(ASN1OctetString.getInstance(extBytes).getOctets());
+
+            return new TrustAnchor(cert, extValue.toASN1Primitive().getEncoded(ASN1Encoding.DER));
+        }
+
+        return new TrustAnchor(cert, null);
+    }
+
+
+    private String getPkitsHome()
+    {
+        return "/PKITS";
+    }
+
+    public PKITSTest withEndEntity(String endCert)
+    {
+        endCert = endCert.replace(" ", "").replace("-", "");
+        this.endCert = loadCert(endCert);
+        return this;
+    }
+
+    public boolean endCertMatchesPathCert(int certIndex)
+    {
+        return endCert.equals(this.pathCert(certIndex));
+    }
+
+    public PKITSTest withInhibitAnyPolicy(boolean inhibitAnyPolicy)
+    {
+        this.inhibitAnyPolicy = inhibitAnyPolicy;
+        return this;
+    }
+
+    public PKITSTest withPolicyMappingInhibited(boolean policyMappingInhibited)
+    {
+        this.policyMappingInhibited = policyMappingInhibited;
+        return this;
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/ECAlgorithms.java b/bcprov/src/main/java/org/bouncycastle/math/ec/ECAlgorithms.java
index 33b8017..0aab9eb 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/ECAlgorithms.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/ECAlgorithms.java
@@ -177,8 +177,9 @@
     }
 
     /**
-     * Simple shift-and-add multiplication. Serves as reference implementation
-     * to verify (possibly faster) implementations, and for very small scalars.
+     * Simple shift-and-add multiplication. Serves as reference implementation to verify (possibly
+     * faster) implementations, and for very small scalars. CAUTION: This implementation is NOT
+     * constant-time in any way. It is only intended to be used for diagnostics.
      * 
      * @param p
      *            The point to multiply.
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/ECCurve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/ECCurve.java
index cbe26ef..8f00c6b 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/ECCurve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/ECCurve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 import java.util.Hashtable;
 import java.util.Random;
 
@@ -107,6 +108,10 @@
 
     public abstract boolean isValidFieldElement(BigInteger x);
 
+    public abstract ECFieldElement randomFieldElement(SecureRandom r);
+
+    public abstract ECFieldElement randomFieldElementMult(SecureRandom r);
+
     public synchronized Config configure()
     {
         return new Config(this.coord, this.endomorphism, this.multiplier);
@@ -346,9 +351,11 @@
     }
 
     /**
-     * Sets the default <code>ECMultiplier</code>, unless already set. 
+     * Sets the default <code>ECMultiplier</code>, unless already set.
+     * 
+     * We avoid synchronizing for performance reasons, so there is no uniqueness guarantee.
      */
-    public synchronized ECMultiplier getMultiplier()
+    public ECMultiplier getMultiplier()
     {
         if (this.multiplier == null)
         {
@@ -585,6 +592,30 @@
             return x != null && x.signum() >= 0 && x.compareTo(this.getField().getCharacteristic()) < 0;
         }
 
+        public ECFieldElement randomFieldElement(SecureRandom r)
+        {
+            /*
+             * NOTE: BigInteger comparisons in the rejection sampling are not constant-time, so we
+             * use the product of two independent elements to mitigate side-channels.
+             */
+            BigInteger p = getField().getCharacteristic();
+            ECFieldElement fe1 = fromBigInteger(implRandomFieldElement(r, p));
+            ECFieldElement fe2 = fromBigInteger(implRandomFieldElement(r, p));
+            return fe1.multiply(fe2);
+        }
+
+        public ECFieldElement randomFieldElementMult(SecureRandom r)
+        {
+            /*
+             * NOTE: BigInteger comparisons in the rejection sampling are not constant-time, so we
+             * use the product of two independent elements to mitigate side-channels.
+             */
+            BigInteger p = getField().getCharacteristic();
+            ECFieldElement fe1 = fromBigInteger(implRandomFieldElementMult(r, p));
+            ECFieldElement fe2 = fromBigInteger(implRandomFieldElementMult(r, p));
+            return fe1.multiply(fe2);
+        }
+
         protected ECPoint decompressPoint(int yTilde, BigInteger X1)
         {
             ECFieldElement x = this.fromBigInteger(X1);
@@ -607,6 +638,28 @@
 
             return this.createRawPoint(x, y);
         }
+
+        private static BigInteger implRandomFieldElement(SecureRandom r, BigInteger p)
+        {
+            BigInteger x;
+            do
+            {
+                x = BigIntegers.createRandomBigInteger(p.bitLength(), r);
+            }
+            while (x.compareTo(p) >= 0);
+            return x;
+        }
+
+        private static BigInteger implRandomFieldElementMult(SecureRandom r, BigInteger p)
+        {
+            BigInteger x;
+            do
+            {
+                x = BigIntegers.createRandomBigInteger(p.bitLength(), r);
+            }
+            while (x.signum() <= 0 || x.compareTo(p) >= 0);
+            return x;
+        }
     }
 
     /**
@@ -642,14 +695,6 @@
             this.coord = FP_DEFAULT_COORDS;
         }
 
-        /**
-         * @deprecated use constructor taking order/cofactor
-         */
-        protected Fp(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b)
-        {
-            this(q, r, a, b, null, null);
-        }
-
         protected Fp(BigInteger q, BigInteger r, ECFieldElement a, ECFieldElement b, BigInteger order, BigInteger cofactor)
         {
             super(q);
@@ -785,11 +830,6 @@
             super(buildField(m, k1, k2, k3));
         }
 
-        public boolean isValidFieldElement(BigInteger x)
-        {
-            return x != null && x.signum() >= 0 && x.bitLength() <= this.getFieldSize();
-        }
-
         public ECPoint createPoint(BigInteger x, BigInteger y)
         {
             ECFieldElement X = this.fromBigInteger(x), Y = this.fromBigInteger(y);
@@ -835,6 +875,29 @@
             return this.createRawPoint(X, Y);
         }
 
+        public boolean isValidFieldElement(BigInteger x)
+        {
+            return x != null && x.signum() >= 0 && x.bitLength() <= this.getFieldSize();
+        }
+
+        public ECFieldElement randomFieldElement(SecureRandom r)
+        {
+            int m = getFieldSize();
+            return fromBigInteger(BigIntegers.createRandomBigInteger(m, r));
+        }
+
+        public ECFieldElement randomFieldElementMult(SecureRandom r)
+        {
+            /*
+             * NOTE: BigInteger comparisons in the rejection sampling are not constant-time, so we
+             * use the product of two independent elements to mitigate side-channels.
+             */
+            int m = getFieldSize();
+            ECFieldElement fe1 = fromBigInteger(implRandomFieldElementMult(r, m));
+            ECFieldElement fe2 = fromBigInteger(implRandomFieldElementMult(r, m));
+            return fe1.multiply(fe2);
+        }
+
         /**
          * Decompresses a compressed point P = (xp, yp) (X9.62 s 4.2.2).
          * 
@@ -971,6 +1034,17 @@
         {
             return this.order != null && this.cofactor != null && this.b.isOne() && (this.a.isZero() || this.a.isOne());
         }
+
+        private static BigInteger implRandomFieldElementMult(SecureRandom r, int m)
+        {
+            BigInteger x;
+            do
+            {
+                x = BigIntegers.createRandomBigInteger(m, r);
+            }
+            while (x.signum() <= 0);
+            return x;
+        }
     }
 
     /**
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/ECFieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/ECFieldElement.java
index 6f1df6d..43c83e0 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/ECFieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/ECFieldElement.java
@@ -3,8 +3,6 @@
 import java.math.BigInteger;
 import java.util.Random;
 
-import org.bouncycastle.math.raw.Mod;
-import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.Integers;
@@ -418,13 +416,7 @@
 
         protected BigInteger modInverse(BigInteger x)
         {
-            int bits = getFieldSize();
-            int len = (bits + 31) >> 5;
-            int[] p = Nat.fromBigInteger(bits, q);
-            int[] n = Nat.fromBigInteger(bits, x);
-            int[] z = Nat.create(len);
-            Mod.invert(p, n, z);
-            return Nat.toBigInteger(len, z);
+            return BigIntegers.modOddInverse(q, x);
         }
 
         protected BigInteger modMult(BigInteger x1, BigInteger x2)
@@ -716,44 +708,6 @@
             return m;
         }
 
-        /**
-         * Checks, if the ECFieldElements <code>a</code> and <code>b</code>
-         * are elements of the same field <code>F<sub>2<sup>m</sup></sub></code>
-         * (having the same representation).
-         * @param a field element.
-         * @param b field element to be compared.
-         * @throws IllegalArgumentException if <code>a</code> and <code>b</code>
-         * are not elements of the same field
-         * <code>F<sub>2<sup>m</sup></sub></code> (having the same
-         * representation).
-         * 
-         * @deprecated Will be removed
-         */
-        public static void checkFieldElements(
-            ECFieldElement a,
-            ECFieldElement b)
-        {
-            if ((!(a instanceof F2m)) || (!(b instanceof F2m)))
-            {
-                throw new IllegalArgumentException("Field elements are not "
-                        + "both instances of ECFieldElement.F2m");
-            }
-
-            ECFieldElement.F2m aF2m = (ECFieldElement.F2m)a;
-            ECFieldElement.F2m bF2m = (ECFieldElement.F2m)b;
-
-            if (aF2m.representation != bF2m.representation)
-            {
-                // Should never occur
-                throw new IllegalArgumentException("One of the F2m field elements has incorrect representation");
-            }
-
-            if ((aF2m.m != bF2m.m) || !Arrays.areEqual(aF2m.ks, bF2m.ks))
-            {
-                throw new IllegalArgumentException("Field elements are not elements of the same field F2m");
-            }
-        }
-
         public ECFieldElement add(final ECFieldElement b)
         {
             // No check performed here for performance reasons. Instead the
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/ECPoint.java b/bcprov/src/main/java/org/bouncycastle/math/ec/ECPoint.java
index 20882da..cc4f63a 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/ECPoint.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/ECPoint.java
@@ -1,8 +1,11 @@
 package org.bouncycastle.math.ec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 import java.util.Hashtable;
 
+import org.bouncycastle.crypto.CryptoServicesRegistrar;
+
 /**
  * base class for points on elliptic curves.
  */
@@ -222,13 +225,31 @@
         }
         default:
         {
-            ECFieldElement Z1 = getZCoord(0);
-            if (Z1.isOne())
+            ECFieldElement z = getZCoord(0);
+            if (z.isOne())
             {
                 return this;
             }
 
-            return normalize(Z1.invert());
+            if (null == curve)
+            {
+                throw new IllegalStateException("Detached points must be in affine coordinates");
+            }
+
+            /*
+             * Use blinding to avoid the side-channel leak identified and analyzed in the paper
+             * "Yet another GCD based inversion side-channel affecting ECC implementations" by Nir
+             * Drucker and Shay Gueron.
+             * 
+             * To blind the calculation of z^-1, choose a multiplicative (i.e. non-zero) field
+             * element 'b' uniformly at random, then calculate the result instead as (z * b)^-1 * b.
+             * Any side-channel in the implementation of 'inverse' now only leaks information about
+             * the value (z * b), and no longer reveals information about 'z' itself.
+             */
+            SecureRandom r = CryptoServicesRegistrar.getSecureRandom();
+            ECFieldElement b = curve.randomFieldElementMult(r);
+            ECFieldElement zInv = z.multiply(b).invert().multiply(b); 
+            return normalize(zInv);
         }
         }
     }
@@ -1883,7 +1904,7 @@
             ECFieldElement X1 = this.x;
             if (X1.isZero()) 
             {
-                // A point with X == 0 is it's own additive inverse
+                // A point with X == 0 is its own additive inverse
                 return curve.getInfinity();
             }
 
@@ -1997,7 +2018,7 @@
             ECFieldElement X1 = this.x;
             if (X1.isZero()) 
             {
-                // A point with X == 0 is it's own additive inverse
+                // A point with X == 0 is its own additive inverse
                 return b;
             }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/WNafUtil.java b/bcprov/src/main/java/org/bouncycastle/math/ec/WNafUtil.java
index aa2c21c..fb4d67e 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/WNafUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/WNafUtil.java
@@ -407,57 +407,6 @@
         return Math.max(2, Math.min(maxWidth, w + 2));
     }
 
-    /**
-     * @deprecated
-     */
-    public static ECPoint mapPointWithPrecomp(ECPoint p, final int minWidth, final boolean includeNegated,
-        final ECPointMap pointMap)
-    {
-        final ECCurve c = p.getCurve();
-        final WNafPreCompInfo infoP = precompute(p, minWidth, includeNegated);
-
-        ECPoint q = pointMap.map(p);
-        c.precompute(q, PRECOMP_NAME, new PreCompCallback()
-        {
-            public PreCompInfo precompute(PreCompInfo existing)
-            {
-                WNafPreCompInfo result = new WNafPreCompInfo();
-
-                result.setConfWidth(infoP.getConfWidth());
-
-                ECPoint twiceP = infoP.getTwice();
-                if (null != twiceP)
-                {
-                    ECPoint twiceQ = pointMap.map(twiceP);
-                    result.setTwice(twiceQ);
-                }
-
-                ECPoint[] preCompP = infoP.getPreComp();
-                ECPoint[] preCompQ = new ECPoint[preCompP.length];
-                for (int i = 0; i < preCompP.length; ++i)
-                {
-                    preCompQ[i] = pointMap.map(preCompP[i]);
-                }
-                result.setPreComp(preCompQ);
-                result.setWidth(infoP.getWidth());
-
-                if (includeNegated)
-                {
-                    ECPoint[] preCompNegQ = new ECPoint[preCompQ.length];
-                    for (int i = 0; i < preCompNegQ.length; ++i)
-                    {
-                        preCompNegQ[i] = preCompQ[i].negate();
-                    }
-                    result.setPreCompNeg(preCompNegQ);
-                }
-
-                return result;
-            }
-        });
-
-        return q;
-    }
-
     public static WNafPreCompInfo precompute(final ECPoint p, final int minWidth, final boolean includeNegated)
     {
         final ECCurve c = p.getCurve();
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519.java
index 82cc7f9..da66f55 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.djb;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -147,4 +148,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        Curve25519Field.random(r, x);
+        return new Curve25519FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        Curve25519Field.randomMult(r, x);
+        return new Curve25519FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519Field.java
index 2e8e335..b34db84 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519Field.java
@@ -1,21 +1,24 @@
 package org.bouncycastle.math.ec.custom.djb;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat256;
+import org.bouncycastle.util.Pack;
 
 public class Curve25519Field
 {
     private static final long M = 0xFFFFFFFFL;
 
-    // 2^255 - 2^4 - 2^1 - 1
+    // 2^255 - 19
     static final int[] P = new int[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0x7FFFFFFF };
     private static final int P7 = 0x7FFFFFFF;
-    private static final int[] PExt = new int[]{ 0x00000169, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
-        0x00000000, 0x00000000, 0x00000000, 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
-        0xFFFFFFFF, 0x3FFFFFFF };
+    private static final int[] PExt = new int[]{ 0x00000169, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        0x00000000, 0x00000000, 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0x3FFFFFFF };
     private static final int PInv = 0x13;
 
     public static void add(int[] x, int[] y, int[] z)
@@ -68,6 +71,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 8; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat256.createExt();
@@ -86,9 +105,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat256.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat256.zero(z);
+            Nat256.sub(P, P, z);
         }
         else
         {
@@ -96,6 +115,27 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[8 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 8);
+            z[7] &= P7;
+        }
+        while (0 == Nat.lessThan(8, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
 //        assert xx[15] >>> 30 == 0;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519FieldElement.java
index 5af2a4e..6f2c161 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/djb/Curve25519FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat256;
 import org.bouncycastle.util.Arrays;
 
@@ -99,7 +98,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat256.create();
-        Mod.invert(Curve25519Field.P, ((Curve25519FieldElement)b).x, z);
+        Curve25519Field.inv(((Curve25519FieldElement)b).x, z);
         Curve25519Field.multiply(z, x, z);
         return new Curve25519FieldElement(z);
     }
@@ -122,7 +121,7 @@
     {
 //        return new Curve25519FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat256.create();
-        Mod.invert(Curve25519Field.P, x, z);
+        Curve25519Field.inv(x, z);
         return new Curve25519FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Curve.java
index 2080049..2894490 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.gm;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SM2P256V1Field.random(r, x);
+        return new SM2P256V1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SM2P256V1Field.randomMult(r, x);
+        return new SM2P256V1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Field.java
index 3304d0d..d0acc9b 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1Field.java
@@ -1,9 +1,12 @@
 package org.bouncycastle.math.ec.custom.gm;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat256;
+import org.bouncycastle.util.Pack;
 
 public class SM2P256V1Field
 {
@@ -12,9 +15,9 @@
     // 2^256 - 2^224 - 2^96 + 2^64 - 1
     static final int[] P = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0xFFFFFFFE };
-    static final int[] PExt = new int[]{ 00000001, 0x00000000, 0xFFFFFFFE, 0x00000001, 0x00000001,
-        0xFFFFFFFE, 0x00000000, 0x00000002, 0xFFFFFFFE, 0xFFFFFFFD, 0x00000003, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
-        0x00000000, 0xFFFFFFFE };
+    private static final int[] PExt = new int[]{ 00000001, 0x00000000, 0xFFFFFFFE, 0x00000001, 0x00000001, 0xFFFFFFFE,
+        0x00000000, 0x00000002, 0xFFFFFFFE, 0xFFFFFFFD, 0x00000003, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000,
+        0xFFFFFFFE };
     private static final int P7s1 = 0xFFFFFFFE >>> 1;
     private static final int PExt15s1 = 0xFFFFFFFE >>> 1;
 
@@ -68,6 +71,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 8; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat256.createExt();
@@ -86,9 +105,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat256.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat256.zero(z);
+            Nat256.sub(P, P, z);
         }
         else
         {
@@ -96,6 +115,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[8 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 8);
+        }
+        while (0 == Nat.lessThan(8, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long xx08 = xx[8] & M, xx09 = xx[9] & M, xx10 = xx[10] & M, xx11 = xx[11] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1FieldElement.java
index 386aedc..8cc2526 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/gm/SM2P256V1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat256;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat256.create();
-        Mod.invert(SM2P256V1Field.P, ((SM2P256V1FieldElement)b).x, z);
+        SM2P256V1Field.inv(((SM2P256V1FieldElement)b).x, z);
         SM2P256V1Field.multiply(z, x, z);
         return new SM2P256V1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SM2P256V1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat256.create();
-        Mod.invert(SM2P256V1Field.P, x, z);
+        SM2P256V1Field.inv(x, z);
         return new SM2P256V1FieldElement(z);
     }
 
@@ -134,7 +133,7 @@
          * Raise this element to the exponent 2^254 - 2^222 - 2^94 + 2^62
          *
          * Breaking up the exponent's binary representation into "repunits", we get:
-         * { 31 1s } { 1 0s } { 128 1s } { 31 0s } { 1 1s } { 62 0s}
+         * { 31 1s } { 1 0s } { 128 1s } { 31 0s } { 1 1s } { 62 0s }
          *
          * We use an addition chain for the beginning: [1], 2, 3, 6, 12, [24], 30, [31] 
          */
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Curve.java
index b47cf0a..4c87cd7 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat128.create();
+        SecP128R1Field.random(r, x);
+        return new SecP128R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat128.create();
+        SecP128R1Field.randomMult(r, x);
+        return new SecP128R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Field.java
index 708f57e..6a3ab2d 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1Field.java
@@ -1,21 +1,24 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat128;
 import org.bouncycastle.math.raw.Nat256;
+import org.bouncycastle.util.Pack;
 
 public class SecP128R1Field
 {
     private static final long M = 0xFFFFFFFFL;
 
     // 2^128 - 2^97 - 1
-    static final int[] P = new int[] { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFD };
-    static final int[] PExt = new int[] { 0x00000001, 0x00000000, 0x00000000, 0x00000004, 0xFFFFFFFE,
-        0xFFFFFFFF, 0x00000003, 0xFFFFFFFC };
-    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFB,
-        0x00000001, 0x00000000, 0xFFFFFFFC, 0x00000003 };
+    static final int[] P = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFD };
+    private static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0x00000004, 0xFFFFFFFE, 0xFFFFFFFF,
+        0x00000003, 0xFFFFFFFC };
+    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFB, 0x00000001,
+        0x00000000, 0xFFFFFFFC, 0x00000003 };
     private static final int P3s1 = 0xFFFFFFFD >>> 1;
     private static final int PExt7s1 = 0xFFFFFFFC >>> 1;
 
@@ -69,6 +72,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 4; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat128.createExt();
@@ -87,9 +106,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat128.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat128.zero(z);
+            Nat128.sub(P, P, z);
         }
         else
         {
@@ -97,6 +116,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[4 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 4);
+        }
+        while (0 == Nat.lessThan(4, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long x0 = xx[0] & M, x1 = xx[1] & M, x2 = xx[2] & M, x3 = xx[3] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1FieldElement.java
index 6185c53..52810dc 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP128R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat128;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat128.create();
-        Mod.invert(SecP128R1Field.P, ((SecP128R1FieldElement)b).x, z);
+        SecP128R1Field.inv(((SecP128R1FieldElement)b).x, z);
         SecP128R1Field.multiply(z, x, z);
         return new SecP128R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP128R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat128.create();
-        Mod.invert(SecP128R1Field.P, x, z);
+        SecP128R1Field.inv(x, z);
         return new SecP128R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160K1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160K1Curve.java
index d9e0902..1b484bf 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160K1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160K1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -142,4 +143,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R2Field.random(r, x);
+        return new SecP160R2FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R2Field.randomMult(r, x);
+        return new SecP160R2FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Curve.java
index e04c3d4..1f5b9ab 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R1Field.random(r, x);
+        return new SecP160R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R1Field.randomMult(r, x);
+        return new SecP160R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Field.java
index 91ba0e5..d46eb37 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1Field.java
@@ -1,20 +1,23 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat160;
+import org.bouncycastle.util.Pack;
 
 public class SecP160R1Field
 {
     private static final long M = 0xFFFFFFFFL;
 
     // 2^160 - 2^31 - 1
-    static final int[] P = new int[] { 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
-    static final int[] PExt = new int[] { 0x00000001, 0x40000001, 0x00000000, 0x00000000, 0x00000000,
-        0xFFFFFFFE, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xBFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
-        0xFFFFFFFF, 0x00000001, 0x00000001 };
+    static final int[] P = new int[]{ 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x00000001, 0x40000001, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFE,
+        0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xBFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0x00000001, 0x00000001 };
     private static final int P4 = 0xFFFFFFFF;
     private static final int PExt9 = 0xFFFFFFFF;
     private static final int PInv = 0x80000001;
@@ -72,6 +75,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 5; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat160.createExt();
@@ -93,9 +112,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat160.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat160.zero(z);
+            Nat160.sub(P, P, z);
         }
         else
         {
@@ -103,6 +122,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[5 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 5);
+        }
+        while (0 == Nat.lessThan(5, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long x5 = xx[5] & M, x6 = xx[6] & M, x7 = xx[7] & M, x8 = xx[8] & M, x9 = xx[9] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1FieldElement.java
index 653b1ed..309ef34 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat160;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat160.create();
-        Mod.invert(SecP160R1Field.P, ((SecP160R1FieldElement)b).x, z);
+        SecP160R1Field.inv(((SecP160R1FieldElement)b).x, z);
         SecP160R1Field.multiply(z, x, z);
         return new SecP160R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP160R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat160.create();
-        Mod.invert(SecP160R1Field.P, x, z);
+        SecP160R1Field.inv(x, z);
         return new SecP160R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Curve.java
index e945e8f..1605657 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R2Field.random(r, x);
+        return new SecP160R2FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat160.create();
+        SecP160R2Field.randomMult(r, x);
+        return new SecP160R2FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Field.java
index 70c5e0c..07431b8 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2Field.java
@@ -1,16 +1,19 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat160;
+import org.bouncycastle.util.Pack;
 
 public class SecP160R2Field
 {
     // 2^160 - 2^32 - 2^14 - 2^12 - 2^9 - 2^8 - 2^7 - 2^3 - 2^2 - 1
     static final int[] P = new int[]{ 0xFFFFAC73, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x1B44BBA9, 0x0000A71A, 0x00000001, 0x00000000, 0x00000000,
-        0xFFFF58E6, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x1B44BBA9, 0x0000A71A, 0x00000001, 0x00000000, 0x00000000, 0xFFFF58E6,
+        0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xE4BB4457, 0xFFFF58E5, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
         0x0000A719, 0x00000002 };
     private static final int P4 = 0xFFFFFFFF;
@@ -70,6 +73,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 5; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat160.createExt();
@@ -91,9 +110,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat160.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat160.zero(z);
+            Nat160.sub(P, P, z);
         }
         else
         {
@@ -101,6 +120,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[5 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 5);
+        }
+        while (0 == Nat.lessThan(5, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long cc = Nat160.mul33Add(PInv33, xx, 5, xx, 0, z, 0);
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2FieldElement.java
index d495b9d..afb79ad 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP160R2FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat160;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat160.create();
-        Mod.invert(SecP160R2Field.P, ((SecP160R2FieldElement)b).x, z);
+        SecP160R2Field.inv(((SecP160R2FieldElement)b).x, z);
         SecP160R2Field.multiply(z, x, z);
         return new SecP160R2FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP160R2FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat160.create();
-        Mod.invert(SecP160R2Field.P, x, z);
+        SecP160R2Field.inv(x, z);
         return new SecP160R2FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Curve.java
index 830cfc0..fa7fe83 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -143,4 +144,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat192.create();
+        SecP192K1Field.random(r, x);
+        return new SecP192K1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat192.create();
+        SecP192K1Field.randomMult(r, x);
+        return new SecP192K1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Field.java
index 1a0bde8..8b6c042 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1Field.java
@@ -1,16 +1,19 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat192;
+import org.bouncycastle.util.Pack;
 
 public class SecP192K1Field
 {
     // 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1
     static final int[] P = new int[]{ 0xFFFFEE37, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x013C4FD1, 0x00002392, 0x00000001, 0x00000000, 0x00000000,
-        0x00000000, 0xFFFFDC6E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x013C4FD1, 0x00002392, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
+        0xFFFFDC6E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xFEC3B02F, 0xFFFFDC6D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0x00002391, 0x00000002 };
     private static final int P5 = 0xFFFFFFFF;
@@ -70,6 +73,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 6; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat192.createExt();
@@ -91,9 +110,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat192.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat192.zero(z);
+            Nat192.sub(P, P, z);
         }
         else
         {
@@ -101,6 +120,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[6 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 6);
+        }
+        while (0 == Nat.lessThan(6, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long cc = Nat192.mul33Add(PInv33, xx, 6, xx, 0, z, 0);
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1FieldElement.java
index 2a13e6b..b499934 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192K1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat192;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat192.create();
-        Mod.invert(SecP192K1Field.P, ((SecP192K1FieldElement)b).x, z);
+        SecP192K1Field.inv(((SecP192K1FieldElement)b).x, z);
         SecP192K1Field.multiply(z, x, z);
         return new SecP192K1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP192K1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat192.create();
-        Mod.invert(SecP192K1Field.P, x, z);
+        SecP192K1Field.inv(x, z);
         return new SecP192K1FieldElement(z);
     }
 
@@ -134,7 +133,7 @@
          * Raise this element to the exponent 2^190 - 2^30 - 2^10 - 2^6 - 2^5 - 2^4 - 2^1
          *
          * Breaking up the exponent's binary representation into "repunits", we get:
-         * { 159 1s } { 1 0s } { 19 1s } { 1 0s } { 3 1s } { 3 0s} { 3 1s } { 1 0s }
+         * { 159 1s } { 1 0s } { 19 1s } { 1 0s } { 3 1s } { 3 0s } { 3 1s } { 1 0s }
          *
          * Therefore we need an addition chain containing 3, 19, 159 (the lengths of the repunits)
          * We use: 1, 2, [3], 6, 8, 16, [19], 35, 70, 140, [159]
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Curve.java
index c1290fe..d7ecc5b 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat192.create();
+        SecP192R1Field.random(r, x);
+        return new SecP192R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat192.create();
+        SecP192R1Field.randomMult(r, x);
+        return new SecP192R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Field.java
index 75e2f5c..5e0dd0d 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1Field.java
@@ -1,9 +1,12 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat192;
+import org.bouncycastle.util.Pack;
 
 public class SecP192R1Field
 {
@@ -11,8 +14,8 @@
 
     // 2^192 - 2^64 - 1
     static final int[] P = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000002, 0x00000000, 0x00000001,
-        0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000002, 0x00000000, 0x00000001, 0x00000000,
+        0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFE,
         0xFFFFFFFF, 0x00000001, 0x00000000, 0x00000002 };
     private static final int P5 = 0xFFFFFFFF;
@@ -71,6 +74,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 6; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat192.createExt();
@@ -92,9 +111,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat192.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat192.zero(z);
+            Nat192.sub(P, P, z);
         }
         else
         {
@@ -102,6 +121,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[6 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 6);
+        }
+        while (0 == Nat.lessThan(6, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long xx06 = xx[6] & M, xx07 = xx[7] & M, xx08 = xx[8] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1FieldElement.java
index 4a1d83c..1540c4a 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP192R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat192;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat192.create();
-        Mod.invert(SecP192R1Field.P, ((SecP192R1FieldElement)b).x, z);
+        SecP192R1Field.inv(((SecP192R1FieldElement)b).x, z);
         SecP192R1Field.multiply(z, x, z);
         return new SecP192R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP192R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat192.create();
-        Mod.invert(SecP192R1Field.P, x, z);
+        SecP192R1Field.inv(x, z);
         return new SecP192R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Curve.java
index 10a1d67..a48dfcb 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -149,4 +150,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat224.create();
+        SecP224K1Field.random(r, x);
+        return new SecP224K1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat224.create();
+        SecP224K1Field.randomMult(r, x);
+        return new SecP224K1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Field.java
index 0146fec..9427f93 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1Field.java
@@ -1,17 +1,20 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat224;
+import org.bouncycastle.util.Pack;
 
 public class SecP224K1Field
 {
     // 2^224 - 2^32 - 2^12 - 2^11 - 2^9 - 2^7 - 2^4 - 2 - 1
     static final int[] P = new int[]{ 0xFFFFE56D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x02C23069, 0x00003526, 0x00000001, 0x00000000, 0x00000000,
-        0x00000000, 0x00000000, 0xFFFFCADA, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x02C23069, 0x00003526, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
+        0x00000000, 0xFFFFCADA, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xFD3DCF97, 0xFFFFCAD9, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0xFFFFFFFF, 0x00003525, 0x00000002 };
     private static final int P6 = 0xFFFFFFFF;
@@ -71,6 +74,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 7; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat224.createExt();
@@ -92,9 +111,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat224.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat224.zero(z);
+            Nat224.sub(P, P, z);
         }
         else
         {
@@ -102,6 +121,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[7 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 7);
+        }
+        while (0 == Nat.lessThan(7, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long cc = Nat224.mul33Add(PInv33, xx, 7, xx, 0, z, 0);
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1FieldElement.java
index dc72044..e4cb310 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224K1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat224;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -101,7 +100,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat224.create();
-        Mod.invert(SecP224K1Field.P, ((SecP224K1FieldElement)b).x, z);
+        SecP224K1Field.inv(((SecP224K1FieldElement)b).x, z);
         SecP224K1Field.multiply(z, x, z);
         return new SecP224K1FieldElement(z);
     }
@@ -124,7 +123,7 @@
     {
 //        return new SecP224K1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat224.create();
-        Mod.invert(SecP224K1Field.P, x, z);
+        SecP224K1Field.inv(x, z);
         return new SecP224K1FieldElement(z);
     }
 
@@ -141,7 +140,7 @@
          * First, raise this element to the exponent 2^221 - 2^29 - 2^9 - 2^8 - 2^6 - 2^4 - 2^1 (i.e. m + 1)
          *
          * Breaking up the exponent's binary representation into "repunits", we get:
-         * { 191 1s } { 1 0s } { 19 1s } { 2 0s } { 1 1s } { 1 0s} { 1 1s } { 1 0s} { 3 1s } { 1 0s}
+         * { 191 1s } { 1 0s } { 19 1s } { 2 0s } { 1 1s } { 1 0s } { 1 1s } { 1 0s } { 3 1s } { 1 0s }
          *
          * Therefore we need an addition chain containing 1, 3, 19, 191 (the lengths of the repunits)
          * We use: [1], 2, [3], 4, 8, 11, [19], 23, 42, 84, 107, [191]
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Curve.java
index ac24ace..97df08c 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat224.create();
+        SecP224R1Field.random(r, x);
+        return new SecP224R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat224.create();
+        SecP224R1Field.randomMult(r, x);
+        return new SecP224R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Field.java
index e05f677..6ffc627 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1Field.java
@@ -1,18 +1,22 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat224;
+import org.bouncycastle.util.Pack;
 
 public class SecP224R1Field
 {
     private static final long M = 0xFFFFFFFFL;
 
     // 2^224 - 2^96 + 1
-    static final int[] P = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF,
-        0xFFFFFFFF, 0x00000000, 0x00000002, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+    static final int[] P = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
+        0x00000000, 0x00000002, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0x00000000,
         0x00000000, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001 };
     private static final int P6 = 0xFFFFFFFF;
@@ -71,6 +75,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 7; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat224.createExt();
@@ -92,9 +112,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat224.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat224.zero(z);
+            Nat224.sub(P, P, z);
         }
         else
         {
@@ -102,6 +122,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[7 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 7);
+        }
+        while (0 == Nat.lessThan(7, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long xx10 = xx[10] & M, xx11 = xx[11] & M, xx12 = xx[12] & M, xx13 = xx[13] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1FieldElement.java
index bd4c871..351e69f 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP224R1FieldElement.java
@@ -98,7 +98,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat224.create();
-        Mod.invert(SecP224R1Field.P, ((SecP224R1FieldElement)b).x, z);
+        SecP224R1Field.inv(((SecP224R1FieldElement)b).x, z);
         SecP224R1Field.multiply(z, x, z);
         return new SecP224R1FieldElement(z);
     }
@@ -121,7 +121,7 @@
     {
 //        return new SecP224R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat224.create();
-        Mod.invert(SecP224R1Field.P, x, z);
+        SecP224R1Field.inv(x, z);
         return new SecP224R1FieldElement(z);
     }
 
@@ -264,7 +264,7 @@
 
             if (Nat224.isZero(d1))
             {
-                Mod.invert(SecP224R1Field.P, e0, t);
+                SecP224R1Field.inv(e0, t);
                 SecP224R1Field.multiply(t, d0, t);
                 return true;
             }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Curve.java
index 2da790a..46a7f0e 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -142,4 +143,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SecP256K1Field.random(r, x);
+        return new SecP256K1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SecP256K1Field.randomMult(r, x);
+        return new SecP256K1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Field.java
index c7b4def..8afbb31 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1Field.java
@@ -1,18 +1,21 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat256;
+import org.bouncycastle.util.Pack;
 
 public class SecP256K1Field
 {
     // 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1
     static final int[] P = new int[]{ 0xFFFFFC2F, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x000E90A1, 0x000007A2, 0x00000001, 0x00000000, 0x00000000,
-        0x00000000, 0x00000000, 0x00000000, 0xFFFFF85E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
-        0xFFFFFFFF, 0xFFFFFFFF };
+    private static final int[] PExt = new int[]{ 0x000E90A1, 0x000007A2, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
+        0x00000000, 0x00000000, 0xFFFFF85E, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF };
     private static final int[] PExtInv = new int[]{ 0xFFF16F5F, 0xFFFFF85D, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000007A1, 0x00000002 };
     private static final int P7 = 0xFFFFFFFF;
@@ -72,6 +75,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 8; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat256.createExt();
@@ -93,9 +112,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat256.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat256.zero(z);
+            Nat256.sub(P, P, z);
         }
         else
         {
@@ -103,6 +122,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[8 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 8);
+        }
+        while (0 == Nat.lessThan(8, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long cc = Nat256.mul33Add(PInv33, xx, 8, xx, 0, z, 0);
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1FieldElement.java
index 645081e..c04ce87 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256K1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat256;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat256.create();
-        Mod.invert(SecP256K1Field.P, ((SecP256K1FieldElement)b).x, z);
+        SecP256K1Field.inv(((SecP256K1FieldElement)b).x, z);
         SecP256K1Field.multiply(z, x, z);
         return new SecP256K1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP256K1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat256.create();
-        Mod.invert(SecP256K1Field.P, x, z);
+        SecP256K1Field.inv(x, z);
         return new SecP256K1FieldElement(z);
     }
 
@@ -135,7 +134,7 @@
          * Raise this element to the exponent 2^254 - 2^30 - 2^7 - 2^6 - 2^5 - 2^4 - 2^2
          *
          * Breaking up the exponent's binary representation into "repunits", we get:
-         * { 223 1s } { 1 0s } { 22 1s } { 4 0s } { 2 1s } { 2 0s}
+         * { 223 1s } { 1 0s } { 22 1s } { 4 0s } { 2 1s } { 2 0s }
          *
          * Therefore we need an addition chain containing 2, 22, 223 (the lengths of the repunits)
          * We use: 1, [2], 3, 6, 9, 11, [22], 44, 88, 176, 220, [223]
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Curve.java
index 485cad5..26f3cb6 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SecP256R1Field.random(r, x);
+        return new SecP256R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat256.create();
+        SecP256R1Field.randomMult(r, x);
+        return new SecP256R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Field.java
index cea1af7..6b780fd 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1Field.java
@@ -1,9 +1,12 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat256;
+import org.bouncycastle.util.Pack;
 
 public class SecP256R1Field
 {
@@ -12,9 +15,9 @@
     // 2^256 - 2^224 + 2^192 + 2^96 - 1
     static final int[] P = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000,
         0x00000001, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF,
-        0xFFFFFFFF, 0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0x00000001, 0xFFFFFFFE,
-        0x00000002, 0xFFFFFFFE };
+    private static final int[] PExt = new int[]{ 0x00000001, 0x00000000, 0x00000000, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0xFFFFFFFE, 0x00000001, 0x00000001, 0xFFFFFFFE, 0x00000002,
+        0xFFFFFFFE };
     private static final int P7 = 0xFFFFFFFF;
     private static final int PExt15s1 = 0xFFFFFFFE >>> 1;
 
@@ -68,6 +71,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 8; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat256.createExt();
@@ -86,9 +105,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat256.isZero(x))
+        if (0 != isZero(x))
         {
-            Nat256.zero(z);
+            Nat256.sub(P, P, z);
         }
         else
         {
@@ -96,6 +115,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[8 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 8);
+        }
+        while (0 == Nat.lessThan(8, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long xx08 = xx[8] & M, xx09 = xx[9] & M, xx10 = xx[10] & M, xx11 = xx[11] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1FieldElement.java
index 6b7f2a5..f2ad785 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP256R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat256;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat256.create();
-        Mod.invert(SecP256R1Field.P, ((SecP256R1FieldElement)b).x, z);
+        SecP256R1Field.inv(((SecP256R1FieldElement)b).x, z);
         SecP256R1Field.multiply(z, x, z);
         return new SecP256R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP256R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat256.create();
-        Mod.invert(SecP256R1Field.P, x, z);
+        SecP256R1Field.inv(x, z);
         return new SecP256R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Curve.java
index f77e27a..3101230 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat.create(12);
+        SecP384R1Field.random(r, x);
+        return new SecP384R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat.create(12);
+        SecP384R1Field.randomMult(r, x);
+        return new SecP384R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Field.java
index 164a795..83852e8 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1Field.java
@@ -1,9 +1,12 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat384;
+import org.bouncycastle.util.Pack;
 
 public class SecP384R1Field
 {
@@ -12,12 +15,12 @@
     // 2^384 - 2^128 - 2^96 + 2^32 - 1
     static final int[] P = new int[]{ 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF,
         0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    static final int[] PExt = new int[]{ 0x00000001, 0xFFFFFFFE, 0x00000000, 0x00000002, 0x00000000, 0xFFFFFFFE,
+    private static final int[] PExt = new int[]{ 0x00000001, 0xFFFFFFFE, 0x00000000, 0x00000002, 0x00000000, 0xFFFFFFFE,
         0x00000000, 0x00000002, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFE, 0x00000001, 0x00000000,
         0xFFFFFFFE, 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
-    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF, 0x00000001,
-        0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF,
-        0x00000001, 0x00000002 };
+    private static final int[] PExtInv = new int[]{ 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFF,
+        0x00000001, 0xFFFFFFFF, 0xFFFFFFFD, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000001, 0xFFFFFFFE,
+        0xFFFFFFFF, 0x00000001, 0x00000002 };
     private static final int P11 = 0xFFFFFFFF;
     private static final int PExt23 = 0xFFFFFFFF;
 
@@ -74,6 +77,22 @@
         }
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 12; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat.create(24);
@@ -83,9 +102,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat.isZero(12, x))
+        if (0 != isZero(x))
         {
-            Nat.zero(12, z);
+            Nat.sub(12, P, P, z);
         }
         else
         {
@@ -93,6 +112,26 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[12 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 12);
+        }
+        while (0 == Nat.lessThan(12, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
         long xx16 = xx[16] & M, xx17 = xx[17] & M, xx18 = xx[18] & M, xx19 = xx[19] & M;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1FieldElement.java
index e17ea59..77623c1 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP384R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat.create(12);
-        Mod.invert(SecP384R1Field.P, ((SecP384R1FieldElement)b).x, z);
+        SecP384R1Field.inv(((SecP384R1FieldElement)b).x, z);
         SecP384R1Field.multiply(z, x, z);
         return new SecP384R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP384R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat.create(12);
-        Mod.invert(SecP384R1Field.P, x, z);
+        SecP384R1Field.inv(x, z);
         return new SecP384R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Curve.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Curve.java
index cc6b283..720044b 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Curve.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Curve.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
 import org.bouncycastle.math.ec.AbstractECLookupTable;
 import org.bouncycastle.math.ec.ECConstants;
@@ -145,4 +146,18 @@
             }
         };
     }
+
+    public ECFieldElement randomFieldElement(SecureRandom r)
+    {
+        int[] x = Nat.create(17);
+        SecP521R1Field.random(r, x);
+        return new SecP521R1FieldElement(x);
+    }
+
+    public ECFieldElement randomFieldElementMult(SecureRandom r)
+    {
+        int[] x = Nat.create(17);
+        SecP521R1Field.randomMult(r, x);
+        return new SecP521R1FieldElement(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Field.java
index 00f1066..62f2591 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1Field.java
@@ -1,9 +1,12 @@
 package org.bouncycastle.math.ec.custom.sec;
 
 import java.math.BigInteger;
+import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat512;
+import org.bouncycastle.util.Pack;
 
 public class SecP521R1Field
 {
@@ -51,6 +54,22 @@
         z[16] = (x16 >>> 1) | (c >>> 23);
     }
 
+    public static void inv(int[] x, int[] z)
+    {
+        Mod.checkedModOddInverse(P, x, z);
+    }
+
+    public static int isZero(int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < 17; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static void multiply(int[] x, int[] y, int[] z)
     {
         int[] tt = Nat.create(33);
@@ -60,9 +79,9 @@
 
     public static void negate(int[] x, int[] z)
     {
-        if (Nat.isZero(17, x))
+        if (0 != isZero(x))
         {
-            Nat.zero(17, z);
+            Nat.sub(17, P, P, z);
         }
         else
         {
@@ -70,6 +89,27 @@
         }
     }
 
+    public static void random(SecureRandom r, int[] z)
+    {
+        byte[] bb = new byte[17 * 4];
+        do
+        {
+            r.nextBytes(bb);
+            Pack.littleEndianToInt(bb, 0, z, 0, 17);
+            z[16] &= P16;
+        }
+        while (0 == Nat.lessThan(17, z, P));
+    }
+
+    public static void randomMult(SecureRandom r, int[] z)
+    {
+        do
+        {
+            random(r, z);
+        }
+        while (0 != isZero(z));
+    }
+
     public static void reduce(int[] xx, int[] z)
     {
 //        assert xx[32] >>> 18 == 0;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1FieldElement.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1FieldElement.java
index 5ba882c..be52b34 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1FieldElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecP521R1FieldElement.java
@@ -3,7 +3,6 @@
 import java.math.BigInteger;
 
 import org.bouncycastle.math.ec.ECFieldElement;
-import org.bouncycastle.math.raw.Mod;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
@@ -97,7 +96,7 @@
     {
 //        return multiply(b.invert());
         int[] z = Nat.create(17);
-        Mod.invert(SecP521R1Field.P, ((SecP521R1FieldElement)b).x, z);
+        SecP521R1Field.inv(((SecP521R1FieldElement)b).x, z);
         SecP521R1Field.multiply(z, x, z);
         return new SecP521R1FieldElement(z);
     }
@@ -120,7 +119,7 @@
     {
 //        return new SecP521R1FieldElement(toBigInteger().modInverse(Q));
         int[] z = Nat.create(17);
-        Mod.invert(SecP521R1Field.P, x, z);
+        SecP521R1Field.inv(x, z);
         return new SecP521R1FieldElement(z);
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113Field.java
index 9d72ff9..4a02b1e 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113Field.java
@@ -90,14 +90,14 @@
 
     public static void multiply(long[] x, long[] y, long[] z)
     {
-        long[] tt = Nat128.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         reduce(tt, z);
     }
 
     public static void multiplyAddToExt(long[] x, long[] y, long[] zz)
     {
-        long[] tt = Nat128.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         addExt(zz, tt, zz);
     }
@@ -183,11 +183,12 @@
         g1  = ((g0 >>> 57) ^ (g1 <<  7)) & M57;
         g0 &= M57;
 
+        long[] u = zz;
         long[] H = new long[6];
 
-        implMulw(f0, g0, H, 0);               // H(0)       57/56 bits
-        implMulw(f1, g1, H, 2);               // H(INF)     57/54 bits
-        implMulw(f0 ^ f1, g0 ^ g1, H, 4);     // H(1)       57/56 bits
+        implMulw(u, f0, g0, H, 0);              // H(0)       57/56 bits
+        implMulw(u, f1, g1, H, 2);              // H(INF)     57/54 bits
+        implMulw(u, f0 ^ f1, g0 ^ g1, H, 4);    // H(1)       57/56 bits
 
         long r  = H[1] ^ H[2];
         long z0 = H[0],
@@ -201,12 +202,11 @@
         zz[3] = (z3 >>> 21);
     }
 
-    protected static void implMulw(long x, long y, long[] z, int zOff)
+    protected static void implMulw(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 57 == 0;
 //        assert y >>> 57 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -240,7 +240,6 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
+        Interleave.expand64To128(x, 0, 2, zz, 0);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R1Point.java
index d273a7e..65be271 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R2Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R2Point.java
index 1c078a8..d6267ce 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R2Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT113R2Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131Field.java
index 52f81cd..7450ea7 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131Field.java
@@ -96,14 +96,14 @@
 
     public static void multiply(long[] x, long[] y, long[] z)
     {
-        long[] tt = Nat192.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         reduce(tt, z);
     }
 
     public static void multiplyAddToExt(long[] x, long[] y, long[] zz)
     {
-        long[] tt = Nat192.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         addExt(zz, tt, zz);
     }
@@ -217,21 +217,22 @@
         g1  = ((g0 >>> 44) ^ (g1 << 20)) & M44;
         g0 &= M44;
 
+        long[] u = zz;
         long[] H = new long[10];
 
-        implMulw(f0, g0, H, 0);               // H(0)       44/43 bits
-        implMulw(f2, g2, H, 2);               // H(INF)     44/41 bits
+        implMulw(u, f0, g0, H, 0);              // H(0)       44/43 bits
+        implMulw(u, f2, g2, H, 2);              // H(INF)     44/41 bits
 
         long t0 = f0 ^ f1 ^ f2;
         long t1 = g0 ^ g1 ^ g2;
 
-        implMulw(t0, t1, H, 4);               // H(1)       44/43 bits
+        implMulw(u, t0, t1, H, 4);              // H(1)       44/43 bits
 
         long t2 = (f1 << 1) ^ (f2 << 2);
         long t3 = (g1 << 1) ^ (g2 << 2);
 
-        implMulw(f0 ^ t2, g0 ^ t3, H, 6);     // H(t)       44/45 bits
-        implMulw(t0 ^ t2, t1 ^ t3, H, 8);     // H(t + 1)   44/45 bits
+        implMulw(u, f0 ^ t2, g0 ^ t3, H, 6);    // H(t)       44/45 bits
+        implMulw(u, t0 ^ t2, t1 ^ t3, H, 8);    // H(t + 1)   44/45 bits
 
         long t4 = H[6] ^ H[8];
         long t5 = H[7] ^ H[9];
@@ -304,12 +305,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulw(long x, long y, long[] z, int zOff)
+    protected static void implMulw(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 45 == 0;
 //        assert y >>> 45 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -321,20 +321,23 @@
 
         int j = (int)x;
         long g, h = 0, l = u[j & 7]
-                         ^ u[(j >>> 3) & 7] << 3
-                         ^ u[(j >>> 6) & 7] << 6;
-        int k = 33;
+                         ^ u[(j >>>  3) & 7] <<  3
+                         ^ u[(j >>>  6) & 7] <<  6
+                         ^ u[(j >>>  9) & 7] <<  9
+                         ^ u[(j >>> 12) & 7] << 12;
+        int k = 30;
         do
         {
             j  = (int)(x >>> k);
             g  = u[j & 7]
-               ^ u[(j >>> 3) & 7] << 3
-               ^ u[(j >>> 6) & 7] << 6
-               ^ u[(j >>> 9) & 7] << 9;
+               ^ u[(j >>>  3) & 7] <<  3
+               ^ u[(j >>>  6) & 7] <<  6
+               ^ u[(j >>>  9) & 7] <<  9
+               ^ u[(j >>> 12) & 7] << 12;
             l ^= (g <<   k);
             h ^= (g >>> -k);
         }
-        while ((k -= 12) > 0);
+        while ((k -= 15) > 0);
 
 //        assert h >>> 25 == 0;
 
@@ -344,8 +347,7 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
+        Interleave.expand64To128(x, 0, 2, zz, 0);
         zz[4] = Interleave.expand8to16((int)x[2]) & 0xFFFFFFFFL;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R1Point.java
index 30da95d..8b5b3f0 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R2Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R2Point.java
index 9f5012a..527dd70 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R2Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT131R2Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163Field.java
index 5dd7950..3f826bf 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163Field.java
@@ -109,14 +109,14 @@
 
     public static void multiply(long[] x, long[] y, long[] z)
     {
-        long[] tt = Nat192.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         reduce(tt, z);
     }
 
     public static void multiplyAddToExt(long[] x, long[] y, long[] zz)
     {
-        long[] tt = Nat192.createExt64();
+        long[] tt = new long[8];
         implMultiply(x, y, tt);
         addExt(zz, tt, zz);
     }
@@ -228,21 +228,22 @@
         g1  = ((g0 >>> 55) ^ (g1 <<  9)) & M55;
         g0 &= M55;
 
+        long[] u = zz;
         long[] H = new long[10];
 
-        implMulw(f0, g0, H, 0);               // H(0)       55/54 bits
-        implMulw(f2, g2, H, 2);               // H(INF)     55/50 bits
+        implMulw(u, f0, g0, H, 0);               // H(0)       55/54 bits
+        implMulw(u, f2, g2, H, 2);               // H(INF)     55/50 bits
 
         long t0 = f0 ^ f1 ^ f2;
         long t1 = g0 ^ g1 ^ g2;
 
-        implMulw(t0, t1, H, 4);               // H(1)       55/54 bits
+        implMulw(u, t0, t1, H, 4);               // H(1)       55/54 bits
 
         long t2 = (f1 << 1) ^ (f2 << 2);
         long t3 = (g1 << 1) ^ (g2 << 2);
 
-        implMulw(f0 ^ t2, g0 ^ t3, H, 6);     // H(t)       55/56 bits
-        implMulw(t0 ^ t2, t1 ^ t3, H, 8);     // H(t + 1)   55/56 bits
+        implMulw(u, f0 ^ t2, g0 ^ t3, H, 6);     // H(t)       55/56 bits
+        implMulw(u, t0 ^ t2, t1 ^ t3, H, 8);     // H(t + 1)   55/56 bits
 
         long t4 = H[6] ^ H[8];
         long t5 = H[7] ^ H[9];
@@ -315,12 +316,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulw(long x, long y, long[] z, int zOff)
+    protected static void implMulw(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 56 == 0;
 //        assert y >>> 56 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -352,8 +352,6 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
-        Interleave.expand64To128(x[2], zz, 4);
+        Interleave.expand64To128(x, 0, 3, zz, 0);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163K1Point.java
index 9f7e2f1..64d3c29 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163K1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -220,7 +220,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R1Point.java
index 67d72f4..3e946ec 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R2Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R2Point.java
index 50c97da..582dde3 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R2Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT163R2Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -220,7 +220,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193Field.java
index b81a038..1271ed9 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193Field.java
@@ -242,10 +242,12 @@
         implExpand(x, f);
         implExpand(y, g);
 
-        implMulwAcc(f[0], g[0], zz, 0);
-        implMulwAcc(f[1], g[1], zz, 1);
-        implMulwAcc(f[2], g[2], zz, 2);
-        implMulwAcc(f[3], g[3], zz, 3);
+        long[] u = new long[8];
+
+        implMulwAcc(u, f[0], g[0], zz, 0);
+        implMulwAcc(u, f[1], g[1], zz, 1);
+        implMulwAcc(u, f[2], g[2], zz, 2);
+        implMulwAcc(u, f[3], g[3], zz, 3);
 
         // U *= (1 - t^n)
         for (int i = 5; i > 0; --i)
@@ -253,8 +255,8 @@
             zz[i] ^= zz[i - 1];
         }
 
-        implMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1);
-        implMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3);
+        implMulwAcc(u, f[0] ^ f[1], g[0] ^ g[1], zz, 1);
+        implMulwAcc(u, f[2] ^ f[3], g[2] ^ g[3], zz, 3);
 
         // V *= (1 - t^2n)
         for (int i = 7; i > 1; --i)
@@ -266,10 +268,10 @@
         {
             long c0 = f[0] ^ f[2], c1 = f[1] ^ f[3];
             long d0 = g[0] ^ g[2], d1 = g[1] ^ g[3];
-            implMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3);
+            implMulwAcc(u, c0 ^ c1, d0 ^ d1, zz, 3);
             long[] t = new long[3];
-            implMulwAcc(c0, d0, t, 0);
-            implMulwAcc(c1, d1, t, 1);
+            implMulwAcc(u, c0, d0, t, 0);
+            implMulwAcc(u, c1, d1, t, 1);
             long t0 = t[0], t1 = t[1], t2 = t[2];
             zz[2] ^= t0;
             zz[3] ^= t0 ^ t1;
@@ -280,12 +282,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulwAcc(long x, long y, long[] z, int zOff)
+    protected static void implMulwAcc(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 49 == 0;
 //        assert y >>> 49 == 0;
 
-        long[] u = new long[8];
 //        u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -320,9 +321,7 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
-        Interleave.expand64To128(x[2], zz, 4);
-        zz[6] = (x[3] & M01);
+        Interleave.expand64To128(x, 0, 3, zz, 0);
+        zz[6] = x[3] & M01;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R1Point.java
index fed5e87..da560ce 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R2Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R2Point.java
index 5f74ee0..b073588 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R2Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT193R2Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -222,7 +222,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233Field.java
index 0fb917c..0e75986 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233Field.java
@@ -254,10 +254,12 @@
         implExpand(x, f);
         implExpand(y, g);
 
-        implMulwAcc(f[0], g[0], zz, 0);
-        implMulwAcc(f[1], g[1], zz, 1);
-        implMulwAcc(f[2], g[2], zz, 2);
-        implMulwAcc(f[3], g[3], zz, 3);
+        long[] u = new long[8];
+
+        implMulwAcc(u, f[0], g[0], zz, 0);
+        implMulwAcc(u, f[1], g[1], zz, 1);
+        implMulwAcc(u, f[2], g[2], zz, 2);
+        implMulwAcc(u, f[3], g[3], zz, 3);
 
         // U *= (1 - t^n)
         for (int i = 5; i > 0; --i)
@@ -265,8 +267,8 @@
             zz[i] ^= zz[i - 1];
         }
 
-        implMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1);
-        implMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3);
+        implMulwAcc(u, f[0] ^ f[1], g[0] ^ g[1], zz, 1);
+        implMulwAcc(u, f[2] ^ f[3], g[2] ^ g[3], zz, 3);
 
         // V *= (1 - t^2n)
         for (int i = 7; i > 1; --i)
@@ -278,10 +280,10 @@
         {
             long c0 = f[0] ^ f[2], c1 = f[1] ^ f[3];
             long d0 = g[0] ^ g[2], d1 = g[1] ^ g[3];
-            implMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3);
+            implMulwAcc(u, c0 ^ c1, d0 ^ d1, zz, 3);
             long[] t = new long[3];
-            implMulwAcc(c0, d0, t, 0);
-            implMulwAcc(c1, d1, t, 1);
+            implMulwAcc(u, c0, d0, t, 0);
+            implMulwAcc(u, c1, d1, t, 1);
             long t0 = t[0], t1 = t[1], t2 = t[2];
             zz[2] ^= t0;
             zz[3] ^= t0 ^ t1;
@@ -292,12 +294,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulwAcc(long x, long y, long[] z, int zOff)
+    protected static void implMulwAcc(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 59 == 0;
 //        assert y >>> 59 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -329,9 +330,6 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
-        Interleave.expand64To128(x[2], zz, 4);
-        Interleave.expand64To128(x[3], zz, 6);
+        Interleave.expand64To128(x, 0, 4, zz, 0);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233K1Point.java
index fe9e890..ccef52b 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233K1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -229,7 +229,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233R1Point.java
index cc4758d..028cf79 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT233R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -220,7 +220,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239Field.java
index 9c520d8..a89f6cf 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239Field.java
@@ -263,10 +263,12 @@
         implExpand(x, f);
         implExpand(y, g);
 
-        implMulwAcc(f[0], g[0], zz, 0);
-        implMulwAcc(f[1], g[1], zz, 1);
-        implMulwAcc(f[2], g[2], zz, 2);
-        implMulwAcc(f[3], g[3], zz, 3);
+        long[] u = new long[8];
+
+        implMulwAcc(u, f[0], g[0], zz, 0);
+        implMulwAcc(u, f[1], g[1], zz, 1);
+        implMulwAcc(u, f[2], g[2], zz, 2);
+        implMulwAcc(u, f[3], g[3], zz, 3);
 
         // U *= (1 - t^n)
         for (int i = 5; i > 0; --i)
@@ -274,8 +276,8 @@
             zz[i] ^= zz[i - 1];
         }
 
-        implMulwAcc(f[0] ^ f[1], g[0] ^ g[1], zz, 1);
-        implMulwAcc(f[2] ^ f[3], g[2] ^ g[3], zz, 3);
+        implMulwAcc(u, f[0] ^ f[1], g[0] ^ g[1], zz, 1);
+        implMulwAcc(u, f[2] ^ f[3], g[2] ^ g[3], zz, 3);
 
         // V *= (1 - t^2n)
         for (int i = 7; i > 1; --i)
@@ -287,10 +289,10 @@
         {
             long c0 = f[0] ^ f[2], c1 = f[1] ^ f[3];
             long d0 = g[0] ^ g[2], d1 = g[1] ^ g[3];
-            implMulwAcc(c0 ^ c1, d0 ^ d1, zz, 3);
+            implMulwAcc(u, c0 ^ c1, d0 ^ d1, zz, 3);
             long[] t = new long[3];
-            implMulwAcc(c0, d0, t, 0);
-            implMulwAcc(c1, d1, t, 1);
+            implMulwAcc(u, c0, d0, t, 0);
+            implMulwAcc(u, c1, d1, t, 1);
             long t0 = t[0], t1 = t[1], t2 = t[2];
             zz[2] ^= t0;
             zz[3] ^= t0 ^ t1;
@@ -301,12 +303,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulwAcc(long x, long y, long[] z, int zOff)
+    protected static void implMulwAcc(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 60 == 0;
 //        assert y >>> 60 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -340,9 +341,6 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
-        Interleave.expand64To128(x[2], zz, 4);
-        Interleave.expand64To128(x[3], zz, 6);
+        Interleave.expand64To128(x, 0, 4, zz, 0);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239K1Point.java
index 15b14aa..e432297 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT239K1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -230,7 +230,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283Field.java
index 064d650..ed9e511 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283Field.java
@@ -11,7 +11,8 @@
     private static final long M27 = -1L >>> 37;
     private static final long M57 = -1L >>> 7;
 
-    private static final long[] ROOT_Z = new long[]{ 0x0C30C30C30C30808L, 0x30C30C30C30C30C3L, 0x820820820820830CL, 0x0820820820820820L, 0x2082082L };
+    private static final long[] ROOT_Z = new long[]{ 0x0C30C30C30C30808L, 0x30C30C30C30C30C3L, 0x820820820820830CL,
+        0x0820820820820820L, 0x2082082L };
 
     public static void add(long[] x, long[] y, long[] z)
     {
@@ -266,32 +267,33 @@
         implExpand(x, a);
         implExpand(y, b);
 
+        long[] u = zz;
         long[] p = new long[26];
 
-        implMulw(a[0], b[0], p, 0);                                 // m1
-        implMulw(a[1], b[1], p, 2);                                 // m2
-        implMulw(a[2], b[2], p, 4);                                 // m3
-        implMulw(a[3], b[3], p, 6);                                 // m4
-        implMulw(a[4], b[4], p, 8);                                 // m5
+        implMulw(u, a[0], b[0], p, 0);                  // m1
+        implMulw(u, a[1], b[1], p, 2);                  // m2
+        implMulw(u, a[2], b[2], p, 4);                  // m3
+        implMulw(u, a[3], b[3], p, 6);                  // m4
+        implMulw(u, a[4], b[4], p, 8);                  // m5
 
         long u0 = a[0] ^ a[1], v0 = b[0] ^ b[1];
         long u1 = a[0] ^ a[2], v1 = b[0] ^ b[2];
         long u2 = a[2] ^ a[4], v2 = b[2] ^ b[4];
         long u3 = a[3] ^ a[4], v3 = b[3] ^ b[4];
 
-        implMulw(u1 ^ a[3], v1 ^ b[3], p, 18);                      // m10
-        implMulw(u2 ^ a[1], v2 ^ b[1], p, 20);                      // m11
+        implMulw(u, u1 ^ a[3], v1 ^ b[3], p, 18);       // m10
+        implMulw(u, u2 ^ a[1], v2 ^ b[1], p, 20);       // m11
 
         long A4 = u0 ^ u3  , B4 = v0 ^ v3;
         long A5 = A4 ^ a[2], B5 = B4 ^ b[2];
 
-        implMulw(A4, B4, p, 22);                                    // m12
-        implMulw(A5, B5, p, 24);                                    // m13
+        implMulw(u, A4, B4, p, 22);                     // m12
+        implMulw(u, A5, B5, p, 24);                     // m13
 
-        implMulw(u0, v0, p, 10);                                    // m6
-        implMulw(u1, v1, p, 12);                                    // m7
-        implMulw(u2, v2, p, 14);                                    // m8
-        implMulw(u3, v3, p, 16);                                    // m9
+        implMulw(u, u0, v0, p, 10);                     // m6
+        implMulw(u, u1, v1, p, 12);                     // m7
+        implMulw(u, u2, v2, p, 14);                     // m8
+        implMulw(u, u3, v3, p, 16);                     // m9
 
 
         // Original method, corresponding to formula (16)
@@ -378,12 +380,11 @@
         implCompactExt(zz);
     }
 
-    protected static void implMulw(long x, long y, long[] z, int zOff)
+    protected static void implMulw(long[] u, long x, long y, long[] z, int zOff)
     {
 //        assert x >>> 57 == 0;
 //        assert y >>> 57 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -417,10 +418,7 @@
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz, 0);
-        Interleave.expand64To128(x[1], zz, 2);
-        Interleave.expand64To128(x[2], zz, 4);
-        Interleave.expand64To128(x[3], zz, 6);
+        Interleave.expand64To128(x, 0, 4, zz, 0);
         zz[8] = Interleave.expand32to64((int)x[4]);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283K1Point.java
index c123a40..e81c7b3 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283K1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -230,7 +230,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283R1Point.java
index 4f6c88f..7fa4174 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT283R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -220,7 +220,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409Field.java
index 37b73aa..c09f70a 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409Field.java
@@ -274,9 +274,8 @@
         zz[10] = (z10 >>> 50) ^ (z11 <<  9);
         zz[11] = (z11 >>> 55) ^ (z12 <<  4)
                               ^ (z13 << 63);
-        zz[12] = (z12 >>> 60)
-               ^ (z13 >>> 1);
-        zz[13] = 0;
+        zz[12] = (z13 >>>  1);
+//        zz[13] = 0;
     }
 
     protected static void implExpand(long[] x, long[] z)
@@ -297,19 +296,69 @@
         implExpand(x, a);
         implExpand(y, b);
 
+        long[] u = new long[8];
         for (int i = 0; i < 7; ++i)
         {
-            implMulwAcc(a, b[i], zz, i);
+            implMulwAcc(u, a[i], b[i], zz, i << 1);
         }
 
+        long v0 = zz[0], v1 = zz[1];
+        v0 ^= zz[ 2]; zz[1] = v0 ^ v1; v1 ^= zz[ 3];
+        v0 ^= zz[ 4]; zz[2] = v0 ^ v1; v1 ^= zz[ 5];
+        v0 ^= zz[ 6]; zz[3] = v0 ^ v1; v1 ^= zz[ 7];
+        v0 ^= zz[ 8]; zz[4] = v0 ^ v1; v1 ^= zz[ 9];
+        v0 ^= zz[10]; zz[5] = v0 ^ v1; v1 ^= zz[11];
+        v0 ^= zz[12]; zz[6] = v0 ^ v1; v1 ^= zz[13];
+
+        long w = v0 ^ v1;
+        zz[ 7] = zz[0] ^ w;
+        zz[ 8] = zz[1] ^ w;
+        zz[ 9] = zz[2] ^ w;
+        zz[10] = zz[3] ^ w;
+        zz[11] = zz[4] ^ w;
+        zz[12] = zz[5] ^ w;
+        zz[13] = zz[6] ^ w;
+
+        implMulwAcc(u, a[0] ^ a[1], b[0] ^ b[1], zz,  1);
+
+        implMulwAcc(u, a[0] ^ a[2], b[0] ^ b[2], zz,  2);
+
+        implMulwAcc(u, a[0] ^ a[3], b[0] ^ b[3], zz,  3);
+        implMulwAcc(u, a[1] ^ a[2], b[1] ^ b[2], zz,  3);
+
+        implMulwAcc(u, a[0] ^ a[4], b[0] ^ b[4], zz,  4);
+        implMulwAcc(u, a[1] ^ a[3], b[1] ^ b[3], zz,  4);
+
+        implMulwAcc(u, a[0] ^ a[5], b[0] ^ b[5], zz,  5);
+        implMulwAcc(u, a[1] ^ a[4], b[1] ^ b[4], zz,  5);
+        implMulwAcc(u, a[2] ^ a[3], b[2] ^ b[3], zz,  5);
+
+        implMulwAcc(u, a[0] ^ a[6], b[0] ^ b[6], zz,  6);
+        implMulwAcc(u, a[1] ^ a[5], b[1] ^ b[5], zz,  6);
+        implMulwAcc(u, a[2] ^ a[4], b[2] ^ b[4], zz,  6);
+
+        implMulwAcc(u, a[1] ^ a[6], b[1] ^ b[6], zz,  7);
+        implMulwAcc(u, a[2] ^ a[5], b[2] ^ b[5], zz,  7);
+        implMulwAcc(u, a[3] ^ a[4], b[3] ^ b[4], zz,  7);
+
+        implMulwAcc(u, a[2] ^ a[6], b[2] ^ b[6], zz,  8);
+        implMulwAcc(u, a[3] ^ a[5], b[3] ^ b[5], zz,  8);
+
+        implMulwAcc(u, a[3] ^ a[6], b[3] ^ b[6], zz,  9);
+        implMulwAcc(u, a[4] ^ a[5], b[4] ^ b[5], zz,  9);
+
+        implMulwAcc(u, a[4] ^ a[6], b[4] ^ b[6], zz, 10);
+
+        implMulwAcc(u, a[5] ^ a[6], b[5] ^ b[6], zz, 11);
+
         implCompactExt(zz);
     }
 
-    protected static void implMulwAcc(long[] xs, long y, long[] z, int zOff)
+    protected static void implMulwAcc(long[] u, long x, long y, long[] z, int zOff)
     {
+//        assert x >>> 59 == 0;
 //        assert y >>> 59 == 0;
 
-        long[] u = new long[8];
 //      u[0] = 0;
         u[1] = y;
         u[2] = u[1] << 1;
@@ -319,41 +368,29 @@
         u[6] = u[3] << 1;
         u[7] = u[6] ^  y;
 
-        for (int i = 0; i < 7; ++i)
+        int j = (int)x;
+        long g, h = 0, l = u[j & 7]
+                         ^ (u[(j >>> 3) & 7] << 3);
+        int k = 54;
+        do
         {
-            long x = xs[i];
-
-//            assert x >>> 59 == 0;
-
-            int j = (int)x;
-            long g, h = 0, l = u[j & 7]
-                             ^ (u[(j >>> 3) & 7] << 3);
-            int k = 54;
-            do
-            {
-                j  = (int)(x >>> k);
-                g  = u[j & 7]
-                   ^ u[(j >>> 3) & 7] << 3;
-                l ^= (g <<   k);
-                h ^= (g >>> -k);
-            }
-            while ((k -= 6) > 0);
-
-//            assert h >>> 53 == 0;
-
-            z[zOff + i    ] ^= l & M59;
-            z[zOff + i + 1] ^= (l >>> 59) ^ (h << 5);
+            j  = (int)(x >>> k);
+            g  = u[j & 7]
+               ^ u[(j >>> 3) & 7] << 3;
+            l ^= (g <<   k);
+            h ^= (g >>> -k);
         }
+        while ((k -= 6) > 0);
+
+//        assert h >>> 53 == 0;
+
+        z[zOff    ] ^= l & M59;
+        z[zOff + 1] ^= (l >>> 59) ^ (h << 5);
     }
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz,  0);
-        Interleave.expand64To128(x[1], zz,  2);
-        Interleave.expand64To128(x[2], zz,  4);
-        Interleave.expand64To128(x[3], zz,  6);
-        Interleave.expand64To128(x[4], zz,  8);
-        Interleave.expand64To128(x[5], zz, 10);
+        Interleave.expand64To128(x, 0, 6, zz,  0);
         zz[12] = Interleave.expand32to64((int)x[6]);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409K1Point.java
index 24d6ca8..125bfc5 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409K1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -230,7 +230,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409R1Point.java
index 37fcb12..06d30ac 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT409R1Point.java
@@ -180,7 +180,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -220,7 +220,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571Field.java
index a03d451..83b1190 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571Field.java
@@ -10,10 +10,9 @@
 {
     private static final long M59 = -1L >>> 5;
 
-    private static final long RM = 0xEF7BDEF7BDEF7BDEL;
-
-    private static final long[] ROOT_Z = new long[]{ 0x2BE1195F08CAFB99L, 0x95F08CAF84657C23L, 0xCAF84657C232BE11L, 0x657C232BE1195F08L,
-        0xF84657C2308CAF84L, 0x7C232BE1195F08CAL, 0xBE1195F08CAF8465L, 0x5F08CAF84657C232L, 0x784657C232BE119L };
+    private static final long[] ROOT_Z = new long[]{ 0x2BE1195F08CAFB99L, 0x95F08CAF84657C23L, 0xCAF84657C232BE11L,
+        0x657C232BE1195F08L, 0xF84657C2308CAF84L, 0x7C232BE1195F08CAL, 0xBE1195F08CAF8465L, 0x5F08CAF84657C232L,
+        0x784657C232BE119L };
 
     public static void add(long[] x, long[] y, long[] z)
     {
@@ -192,7 +191,7 @@
         }
 
         /*
-         * Second section with all 4-bit products of B shifted 4 bits
+         * Second section with all 4-bit products of x shifted 4 bits
          */
         Nat.shiftUpBits64(len, t, 0, 4, 0L, t, len);
 
@@ -290,14 +289,87 @@
 
     protected static void implMultiply(long[] x, long[] y, long[] zz)
     {
-//        for (int i = 0; i < 9; ++i)
-//        {
-//            implMulwAcc(x, y[i], zz, i);
-//        }
+//        long[] precomp = precompMultiplicand(y);
+//
+//        implMultiplyPrecomp(x, precomp, zz);
 
-        long[] precomp = precompMultiplicand(y);
-        
-        implMultiplyPrecomp(x, precomp, zz);
+        long[] u = new long[16];
+        for (int i = 0; i < 9; ++i)
+        {
+            implMulwAcc(u, x[i], y[i], zz, i << 1);
+        }
+
+        long v0 = zz[0], v1 = zz[1];
+        v0 ^= zz[ 2]; zz[1] = v0 ^ v1; v1 ^= zz[ 3];
+        v0 ^= zz[ 4]; zz[2] = v0 ^ v1; v1 ^= zz[ 5];
+        v0 ^= zz[ 6]; zz[3] = v0 ^ v1; v1 ^= zz[ 7];
+        v0 ^= zz[ 8]; zz[4] = v0 ^ v1; v1 ^= zz[ 9];
+        v0 ^= zz[10]; zz[5] = v0 ^ v1; v1 ^= zz[11];
+        v0 ^= zz[12]; zz[6] = v0 ^ v1; v1 ^= zz[13];
+        v0 ^= zz[14]; zz[7] = v0 ^ v1; v1 ^= zz[15];
+        v0 ^= zz[16]; zz[8] = v0 ^ v1; v1 ^= zz[17];
+
+        long w = v0 ^ v1;
+        zz[ 9] = zz[0] ^ w;
+        zz[10] = zz[1] ^ w;
+        zz[11] = zz[2] ^ w;
+        zz[12] = zz[3] ^ w;
+        zz[13] = zz[4] ^ w;
+        zz[14] = zz[5] ^ w;
+        zz[15] = zz[6] ^ w;
+        zz[16] = zz[7] ^ w;
+        zz[17] = zz[8] ^ w;
+
+        implMulwAcc(u, x[0] ^ x[1], y[0] ^ y[1], zz,  1);
+
+        implMulwAcc(u, x[0] ^ x[2], y[0] ^ y[2], zz,  2);
+
+        implMulwAcc(u, x[0] ^ x[3], y[0] ^ y[3], zz,  3);
+        implMulwAcc(u, x[1] ^ x[2], y[1] ^ y[2], zz,  3);
+
+        implMulwAcc(u, x[0] ^ x[4], y[0] ^ y[4], zz,  4);
+        implMulwAcc(u, x[1] ^ x[3], y[1] ^ y[3], zz,  4);
+
+        implMulwAcc(u, x[0] ^ x[5], y[0] ^ y[5], zz,  5);
+        implMulwAcc(u, x[1] ^ x[4], y[1] ^ y[4], zz,  5);
+        implMulwAcc(u, x[2] ^ x[3], y[2] ^ y[3], zz,  5);
+
+        implMulwAcc(u, x[0] ^ x[6], y[0] ^ y[6], zz,  6);
+        implMulwAcc(u, x[1] ^ x[5], y[1] ^ y[5], zz,  6);
+        implMulwAcc(u, x[2] ^ x[4], y[2] ^ y[4], zz,  6);
+
+        implMulwAcc(u, x[0] ^ x[7], y[0] ^ y[7], zz,  7);
+        implMulwAcc(u, x[1] ^ x[6], y[1] ^ y[6], zz,  7);
+        implMulwAcc(u, x[2] ^ x[5], y[2] ^ y[5], zz,  7);
+        implMulwAcc(u, x[3] ^ x[4], y[3] ^ y[4], zz,  7);
+
+        implMulwAcc(u, x[0] ^ x[8], y[0] ^ y[8], zz,  8);
+        implMulwAcc(u, x[1] ^ x[7], y[1] ^ y[7], zz,  8);
+        implMulwAcc(u, x[2] ^ x[6], y[2] ^ y[6], zz,  8);
+        implMulwAcc(u, x[3] ^ x[5], y[3] ^ y[5], zz,  8);
+
+        implMulwAcc(u, x[1] ^ x[8], y[1] ^ y[8], zz,  9);
+        implMulwAcc(u, x[2] ^ x[7], y[2] ^ y[7], zz,  9);
+        implMulwAcc(u, x[3] ^ x[6], y[3] ^ y[6], zz,  9);
+        implMulwAcc(u, x[4] ^ x[5], y[4] ^ y[5], zz,  9);
+
+        implMulwAcc(u, x[2] ^ x[8], y[2] ^ y[8], zz, 10);
+        implMulwAcc(u, x[3] ^ x[7], y[3] ^ y[7], zz, 10);
+        implMulwAcc(u, x[4] ^ x[6], y[4] ^ y[6], zz, 10);
+
+        implMulwAcc(u, x[3] ^ x[8], y[3] ^ y[8], zz, 11);
+        implMulwAcc(u, x[4] ^ x[7], y[4] ^ y[7], zz, 11);
+        implMulwAcc(u, x[5] ^ x[6], y[5] ^ y[6], zz, 11);
+
+        implMulwAcc(u, x[4] ^ x[8], y[4] ^ y[8], zz, 12);
+        implMulwAcc(u, x[5] ^ x[7], y[5] ^ y[7], zz, 12);
+
+        implMulwAcc(u, x[5] ^ x[8], y[5] ^ y[8], zz, 13);
+        implMulwAcc(u, x[6] ^ x[7], y[6] ^ y[7], zz, 13);
+
+        implMulwAcc(u, x[6] ^ x[8], y[6] ^ y[8], zz, 14);
+
+        implMulwAcc(u, x[7] ^ x[8], y[7] ^ y[8], zz, 15);
     }
 
     protected static void implMultiplyPrecomp(long[] x, long[] precomp, long[] zz)
@@ -336,60 +408,44 @@
         }
     }
 
-    protected static void implMulwAcc(long[] xs, long y, long[] z, int zOff)
+    protected static void implMulwAcc(long[] u, long x, long y, long[] z, int zOff)
     {
-        long[] u = new long[32];
 //      u[0] = 0;
         u[1] = y;
-        for (int i = 2; i < 32; i += 2)
+        for (int i = 2; i < 16; i += 2)
         {
             u[i    ] = u[i >>> 1] << 1;
             u[i + 1] = u[i      ] ^  y;
         }
 
-        long l = 0;
-        for (int i = 0; i < 9; ++i)
+        int j = (int)x;
+        long g, h = 0, l = u[j & 15]
+                         ^ u[(j >>> 4) & 15] << 4;
+        int k = 56;
+        do
         {
-            long x = xs[i];
-
-            int j = (int)x;
-
-            l ^= u[j & 31];
-
-            long g, h = 0;
-            int k = 60;
-            do
-            {
-                j  = (int)(x >>> k);
-                g  = u[j & 31];
-                l ^= (g <<   k);
-                h ^= (g >>> -k);
-            }
-            while ((k -= 5) > 0);
-
-            for (int p = 0; p < 4; ++p)
-            {
-                x = (x & RM) >>> 1;
-                h ^= x & ((y << p) >> 63);
-            }
-
-            z[zOff + i] ^= l;
-
-            l = h;
+            j  = (int)(x >>> k);
+            g  = u[j & 15]
+               ^ u[(j >>> 4) & 15] << 4;
+            l ^= (g << k);
+            h ^= (g >>> -k);
         }
-        z[zOff + 9] ^= l;
+        while ((k -= 8) > 0);
+
+        for (int p = 0; p < 7; ++p)
+        {
+            x = (x & 0xFEFEFEFEFEFEFEFEL) >>> 1;
+            h ^= x & ((y << p) >> 63);
+        }
+
+//        assert h >>> 63 == 0;
+
+        z[zOff    ] ^= l;
+        z[zOff + 1] ^= h;
     }
 
     protected static void implSquare(long[] x, long[] zz)
     {
-        Interleave.expand64To128(x[0], zz,  0);
-        Interleave.expand64To128(x[1], zz,  2);
-        Interleave.expand64To128(x[2], zz,  4);
-        Interleave.expand64To128(x[3], zz,  6);
-        Interleave.expand64To128(x[4], zz,  8);
-        Interleave.expand64To128(x[5], zz, 10);
-        Interleave.expand64To128(x[6], zz, 12);
-        Interleave.expand64To128(x[7], zz, 14);
-        Interleave.expand64To128(x[8], zz, 16);
+        Interleave.expand64To128(x, 0, 9, zz,  0);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571K1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571K1Point.java
index 4d638df..cc45c3c 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571K1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571K1Point.java
@@ -216,7 +216,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -266,7 +266,7 @@
         ECFieldElement X1 = this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571R1Point.java b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571R1Point.java
index 33612f6..7a446c4 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571R1Point.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/custom/sec/SecT571R1Point.java
@@ -217,7 +217,7 @@
         SecT571FieldElement X1 = (SecT571FieldElement)this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return curve.getInfinity();
         }
 
@@ -294,7 +294,7 @@
         SecT571FieldElement X1 = (SecT571FieldElement)this.x;
         if (X1.isZero())
         {
-            // A point with X == 0 is it's own additive inverse
+            // A point with X == 0 is its own additive inverse
             return b;
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519.java
index fb69601..61b50c7 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519.java
@@ -16,6 +16,8 @@
     public static final int POINT_SIZE = 32;
     public static final int SCALAR_SIZE = 32;
 
+    private static class F extends X25519Field {};
+
     private static final int C_A = 486662;
     private static final int C_A24 = (C_A + 2)/4;
 
@@ -65,17 +67,17 @@
 
     private static void pointDouble(int[] x, int[] z)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
 
-        X25519Field.apm(x, z, A, B);
-        X25519Field.sqr(A, A);
-        X25519Field.sqr(B, B);
-        X25519Field.mul(A, B, x);
-        X25519Field.sub(A, B, A);
-        X25519Field.mul(A, C_A24, z);
-        X25519Field.add(z, B, z);
-        X25519Field.mul(z, A, z);
+        F.apm(x, z, a, b);
+        F.sqr(a, a);
+        F.sqr(b, b);
+        F.mul(a, b, x);
+        F.sub(a, b, a);
+        F.mul(a, C_A24, z);
+        F.add(z, b, z);
+        F.mul(z, a, z);
     }
 
     public static void precompute()
@@ -85,47 +87,47 @@
 
     public static void scalarMult(byte[] k, int kOff, byte[] u, int uOff, byte[] r, int rOff)
     {
-        int[] n = new int[8];   decodeScalar(k, kOff, n);
+        int[] n = new int[8];       decodeScalar(k, kOff, n);
 
-        int[] x1 = X25519Field.create();        X25519Field.decode(u, uOff, x1);
-        int[] x2 = X25519Field.create();        X25519Field.copy(x1, 0, x2, 0);
-        int[] z2 = X25519Field.create();        z2[0] = 1;
-        int[] x3 = X25519Field.create();        x3[0] = 1;
-        int[] z3 = X25519Field.create();
+        int[] x1 = F.create();      F.decode(u, uOff, x1);
+        int[] x2 = F.create();      F.copy(x1, 0, x2, 0);
+        int[] z2 = F.create();      z2[0] = 1;
+        int[] x3 = F.create();      x3[0] = 1;
+        int[] z3 = F.create();
 
-        int[] t1 = X25519Field.create();
-        int[] t2 = X25519Field.create();
+        int[] t1 = F.create();
+        int[] t2 = F.create();
 
 //        assert n[7] >>> 30 == 1;
 
         int bit = 254, swap = 1;
         do
         {
-            X25519Field.apm(x3, z3, t1, x3);
-            X25519Field.apm(x2, z2, z3, x2);
-            X25519Field.mul(t1, x2, t1);
-            X25519Field.mul(x3, z3, x3);
-            X25519Field.sqr(z3, z3);
-            X25519Field.sqr(x2, x2);
+            F.apm(x3, z3, t1, x3);
+            F.apm(x2, z2, z3, x2);
+            F.mul(t1, x2, t1);
+            F.mul(x3, z3, x3);
+            F.sqr(z3, z3);
+            F.sqr(x2, x2);
 
-            X25519Field.sub(z3, x2, t2);
-            X25519Field.mul(t2, C_A24, z2);
-            X25519Field.add(z2, x2, z2);
-            X25519Field.mul(z2, t2, z2);
-            X25519Field.mul(x2, z3, x2);
+            F.sub(z3, x2, t2);
+            F.mul(t2, C_A24, z2);
+            F.add(z2, x2, z2);
+            F.mul(z2, t2, z2);
+            F.mul(x2, z3, x2);
 
-            X25519Field.apm(t1, x3, x3, z3);
-            X25519Field.sqr(x3, x3);
-            X25519Field.sqr(z3, z3);
-            X25519Field.mul(z3, x1, z3);
+            F.apm(t1, x3, x3, z3);
+            F.sqr(x3, x3);
+            F.sqr(z3, z3);
+            F.mul(z3, x1, z3);
 
             --bit;
 
             int word = bit >>> 5, shift = bit & 0x1F;
             int kt = (n[word] >>> shift) & 1;
             swap ^= kt;
-            X25519Field.cswap(swap, x2, x3);
-            X25519Field.cswap(swap, z2, z3);
+            F.cswap(swap, x2, x3);
+            F.cswap(swap, z2, z3);
             swap = kt;
         }
         while (bit >= 3);
@@ -137,26 +139,26 @@
             pointDouble(x2, z2);
         }
 
-        X25519Field.inv(z2, z2);
-        X25519Field.mul(x2, z2, x2);
+        F.inv(z2, z2);
+        F.mul(x2, z2, x2);
 
-        X25519Field.normalize(x2);
-        X25519Field.encode(x2, r, rOff);
+        F.normalize(x2);
+        F.encode(x2, r, rOff);
     }
 
     public static void scalarMultBase(byte[] k, int kOff, byte[] r, int rOff)
     {
-        int[] y = X25519Field.create();
-        int[] z = X25519Field.create();
+        int[] y = F.create();
+        int[] z = F.create();
 
         Ed25519.scalarMultBaseYZ(Friend.INSTANCE, k, kOff, y, z);
 
-        X25519Field.apm(z, y, y, z);
+        F.apm(z, y, y, z);
 
-        X25519Field.inv(z, z);
-        X25519Field.mul(y, z, y);
+        F.inv(z, z);
+        F.mul(y, z, y);
 
-        X25519Field.normalize(y);
-        X25519Field.encode(y, r, rOff);
+        F.normalize(y);
+        F.encode(y, r, rOff);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519Field.java
index cfd7b59..f315a1f 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X25519Field.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.math.ec.rfc7748;
 
+import org.bouncycastle.math.raw.Mod;
+
 public abstract class X25519Field
 {
     public static final int SIZE = 10;
@@ -8,6 +10,8 @@
     private static final int M25 = 0x01FFFFFF;
     private static final int M26 = 0x03FFFFFF;
 
+    private static final int[] P32 = new int[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF, 0x7FFFFFFF };
     private static final int[] ROOT_NEG_ONE = new int[]{ 0x020EA0B0, 0x0386C9D2, 0x00478C4E, 0x0035697F, 0x005E8630,
         0x01FBD7A7, 0x0340264F, 0x01F0B2B4, 0x00027E0E, 0x00570649 };
 
@@ -46,6 +50,11 @@
         int z0 = z[0], z1 = z[1], z2 = z[2], z3 = z[3], z4 = z[4];
         int z5 = z[5], z6 = z[6], z7 = z[7], z8 = z[8], z9 = z[9];
 
+        z2 += (z1 >> 26); z1 &= M26;
+        z4 += (z3 >> 26); z3 &= M26;
+        z7 += (z6 >> 26); z6 &= M26;
+        z9 += (z8 >> 26); z8 &= M26;
+
         z3 += (z2 >> 25); z2 &= M25;
         z5 += (z4 >> 25); z4 &= M25;
         z8 += (z7 >> 25); z7 &= M25;
@@ -120,6 +129,13 @@
         }
     }
 
+    public static void decode(int[] x, int xOff, int[] z)
+    {
+        decode128(x, xOff, z, 0);
+        decode128(x, xOff + 4, z, 5);
+        z[9] &= M24;
+    }
+
     public static void decode(byte[] x, int xOff, int[] z)
     {
         decode128(x, xOff, z, 0);
@@ -127,6 +143,17 @@
         z[9] &= M24;
     }
 
+    private static void decode128(int[] is, int off, int[] z, int zOff)
+    {
+        int t0 = is[off + 0], t1 = is[off + 1], t2 = is[off + 2], t3 = is[off + 3];
+
+        z[zOff + 0] = t0 & M26;
+        z[zOff + 1] = ((t1 <<  6) | (t0 >>> 26)) & M26;
+        z[zOff + 2] = ((t2 << 12) | (t1 >>> 20)) & M25;
+        z[zOff + 3] = ((t3 << 19) | (t2 >>> 13)) & M26;
+        z[zOff + 4] = t3 >>> 7;
+    }
+
     private static void decode128(byte[] bs, int off, int[] z, int zOff)
     {
         int t0 = decode32(bs, off + 0);
@@ -150,12 +177,28 @@
         return n;
     }
 
+    public static void encode(int[] x, int[] z, int zOff)
+    {
+        encode128(x, 0, z, zOff);
+        encode128(x, 5, z, zOff + 4);
+    }
+
     public static void encode(int[] x, byte[] z, int zOff)
     {
         encode128(x, 0, z, zOff);
         encode128(x, 5, z, zOff + 16);
     }
 
+    private static void encode128(int[] x, int xOff, int[] is, int off)
+    {
+        int x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3], x4 = x[xOff + 4];
+
+        is[off + 0] =  x0         | (x1 << 26);
+        is[off + 1] = (x1 >>>  6) | (x2 << 20);
+        is[off + 2] = (x2 >>> 12) | (x3 << 13);
+        is[off + 3] = (x3 >>> 19) | (x4 <<  7);
+    }
+
     private static void encode128(int[] x, int xOff, byte[] bs, int off)
     {
         int x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3], x4 = x[xOff + 4];
@@ -176,15 +219,36 @@
 
     public static void inv(int[] x, int[] z)
     {
-        // z = x^(p-2) = x^7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB
-        // (250 1s) (1 0s) (1 1s) (1 0s) (2 1s)
-        // Addition chain: [1] [2] 3 5 10 15 25 50 75 125 [250]
+//        int[] x2 = create();
+//        int[] t = create();
+//        powPm5d8(x, x2, t);
+//        sqr(t, 3, t);
+//        mul(t, x2, z);
 
-        int[] x2 = create();
         int[] t = create();
-        powPm5d8(x, x2, t);
-        sqr(t, 3, t);
-        mul(t, x2, z);
+        int[] u = new int[8];
+
+        copy(x, 0, t, 0);
+        normalize(t);
+        encode(t, u, 0);
+
+        Mod.modOddInverse(P32, u, u);
+
+        decode(u, 0, z);
+    }
+
+    public static void invVar(int[] x, int[] z)
+    {
+        int[] t = create();
+        int[] u = new int[8];
+
+        copy(x, 0, t, 0);
+        normalize(t);
+        encode(t, u, 0);
+
+        Mod.modOddInverseVar(P32, u, u);
+
+        decode(u, 0, z);
     }
 
     public static int isZero(int[] x)
@@ -434,22 +498,22 @@
         mul(t, x, rz);
     }
 
-    private static void reduce(int[] z, int c)
+    private static void reduce(int[] z, int x)
     {
-        int z9 = z[9], t = z9;
-                   z9   = t & M24; t >>= 24;
-        t += c;
-        t *= 19;
-        t += z[0]; z[0] = t & M26; t >>= 26;
-        t += z[1]; z[1] = t & M26; t >>= 26;
-        t += z[2]; z[2] = t & M25; t >>= 25;
-        t += z[3]; z[3] = t & M26; t >>= 26;
-        t += z[4]; z[4] = t & M25; t >>= 25;
-        t += z[5]; z[5] = t & M26; t >>= 26;
-        t += z[6]; z[6] = t & M26; t >>= 26;
-        t += z[7]; z[7] = t & M25; t >>= 25;
-        t += z[8]; z[8] = t & M26; t >>= 26;
-        t += z9;   z[9] = t;
+        int t = z[9], z9 = t & M24;
+        t = (t >> 24) + x;
+
+        long cc = t * 19;
+        cc += z[0]; z[0] = (int)cc & M26; cc >>= 26;
+        cc += z[1]; z[1] = (int)cc & M26; cc >>= 26;
+        cc += z[2]; z[2] = (int)cc & M25; cc >>= 25;
+        cc += z[3]; z[3] = (int)cc & M26; cc >>= 26;
+        cc += z[4]; z[4] = (int)cc & M25; cc >>= 25;
+        cc += z[5]; z[5] = (int)cc & M26; cc >>= 26;
+        cc += z[6]; z[6] = (int)cc & M26; cc >>= 26;
+        cc += z[7]; z[7] = (int)cc & M25; cc >>= 25;
+        cc += z[8]; z[8] = (int)cc & M26; cc >>= 26;
+        z[9] = z9 + (int)cc;
     }
 
     public static void sqr(int[] x, int[] z)
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448.java
index dda38b8..97bb4ee 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448.java
@@ -16,6 +16,8 @@
     public static final int POINT_SIZE = 56;
     public static final int SCALAR_SIZE = 56;
 
+    private static class F extends X448Field {};
+
     private static final int C_A = 156326;
     private static final int C_A24 = (C_A + 2)/4;
 
@@ -64,19 +66,19 @@
 
     private static void pointDouble(int[] x, int[] z)
     {
-        int[] A = X448Field.create();
-        int[] B = X448Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
 
-//        X448Field.apm(x, z, A, B);
-        X448Field.add(x, z, A);
-        X448Field.sub(x, z, B);
-        X448Field.sqr(A, A);
-        X448Field.sqr(B, B);
-        X448Field.mul(A, B, x);
-        X448Field.sub(A, B, A);
-        X448Field.mul(A, C_A24, z);
-        X448Field.add(z, B, z);
-        X448Field.mul(z, A, z);
+//        F.apm(x, z, a, b);
+        F.add(x, z, a);
+        F.sub(x, z, b);
+        F.sqr(a, a);
+        F.sqr(b, b);
+        F.mul(a, b, x);
+        F.sub(a, b, a);
+        F.mul(a, C_A24, z);
+        F.add(z, b, z);
+        F.mul(z, a, z);
     }
 
     public static void precompute()
@@ -86,54 +88,54 @@
 
     public static void scalarMult(byte[] k, int kOff, byte[] u, int uOff, byte[] r, int rOff)
     {
-        int[] n = new int[14];  decodeScalar(k, kOff, n);
+        int[] n = new int[14];      decodeScalar(k, kOff, n);
 
-        int[] x1 = X448Field.create();        X448Field.decode(u, uOff, x1);
-        int[] x2 = X448Field.create();        X448Field.copy(x1, 0, x2, 0);
-        int[] z2 = X448Field.create();        z2[0] = 1;
-        int[] x3 = X448Field.create();        x3[0] = 1;
-        int[] z3 = X448Field.create();
+        int[] x1 = F.create();      F.decode(u, uOff, x1);
+        int[] x2 = F.create();      F.copy(x1, 0, x2, 0);
+        int[] z2 = F.create();      z2[0] = 1;
+        int[] x3 = F.create();      x3[0] = 1;
+        int[] z3 = F.create();
 
-        int[] t1 = X448Field.create();
-        int[] t2 = X448Field.create();
+        int[] t1 = F.create();
+        int[] t2 = F.create();
 
 //        assert n[13] >>> 31 == 1;
 
         int bit = 447, swap = 1;
         do
         {
-//            X448Field.apm(x3, z3, t1, x3);
-            X448Field.add(x3, z3, t1);
-            X448Field.sub(x3, z3, x3);
-//            X448Field.apm(x2, z2, z3, x2);
-            X448Field.add(x2, z2, z3);
-            X448Field.sub(x2, z2, x2);
+//            F.apm(x3, z3, t1, x3);
+            F.add(x3, z3, t1);
+            F.sub(x3, z3, x3);
+//            F.apm(x2, z2, z3, x2);
+            F.add(x2, z2, z3);
+            F.sub(x2, z2, x2);
 
-            X448Field.mul(t1, x2, t1);
-            X448Field.mul(x3, z3, x3);
-            X448Field.sqr(z3, z3);
-            X448Field.sqr(x2, x2);
+            F.mul(t1, x2, t1);
+            F.mul(x3, z3, x3);
+            F.sqr(z3, z3);
+            F.sqr(x2, x2);
 
-            X448Field.sub(z3, x2, t2);
-            X448Field.mul(t2, C_A24, z2);
-            X448Field.add(z2, x2, z2);
-            X448Field.mul(z2, t2, z2);
-            X448Field.mul(x2, z3, x2);
+            F.sub(z3, x2, t2);
+            F.mul(t2, C_A24, z2);
+            F.add(z2, x2, z2);
+            F.mul(z2, t2, z2);
+            F.mul(x2, z3, x2);
 
-//            X448Field.apm(t1, x3, x3, z3);
-            X448Field.sub(t1, x3, z3);
-            X448Field.add(t1, x3, x3);
-            X448Field.sqr(x3, x3);
-            X448Field.sqr(z3, z3);
-            X448Field.mul(z3, x1, z3);
+//            F.apm(t1, x3, x3, z3);
+            F.sub(t1, x3, z3);
+            F.add(t1, x3, x3);
+            F.sqr(x3, x3);
+            F.sqr(z3, z3);
+            F.mul(z3, x1, z3);
 
             --bit;
 
             int word = bit >>> 5, shift = bit & 0x1F;
             int kt = (n[word] >>> shift) & 1;
             swap ^= kt;
-            X448Field.cswap(swap, x2, x3);
-            X448Field.cswap(swap, z2, z3);
+            F.cswap(swap, x2, x3);
+            F.cswap(swap, z2, z3);
             swap = kt;
         }
         while (bit >= 2);
@@ -145,25 +147,25 @@
             pointDouble(x2, z2);
         }
 
-        X448Field.inv(z2, z2);
-        X448Field.mul(x2, z2, x2);
+        F.inv(z2, z2);
+        F.mul(x2, z2, x2);
 
-        X448Field.normalize(x2);
-        X448Field.encode(x2, r, rOff);
+        F.normalize(x2);
+        F.encode(x2, r, rOff);
     }
 
     public static void scalarMultBase(byte[] k, int kOff, byte[] r, int rOff)
     {
-        int[] x = X448Field.create();
-        int[] y = X448Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
 
         Ed448.scalarMultBaseXY(Friend.INSTANCE, k, kOff, x, y);
 
-        X448Field.inv(x, x);
-        X448Field.mul(x, y, x);
-        X448Field.sqr(x, x);
+        F.inv(x, x);
+        F.mul(x, y, x);
+        F.sqr(x, x);
 
-        X448Field.normalize(x);
-        X448Field.encode(x, r, rOff);
+        F.normalize(x);
+        F.encode(x, r, rOff);
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448Field.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448Field.java
index 53031dc..6e7b8f2 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448Field.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc7748/X448Field.java
@@ -1,5 +1,7 @@
 package org.bouncycastle.math.ec.rfc7748;
 
+import org.bouncycastle.math.raw.Mod;
+
 public abstract class X448Field
 {
     public static final int SIZE = 16;
@@ -7,6 +9,9 @@
     private static final int M28 = 0x0FFFFFFF;
     private static final long U32 = 0xFFFFFFFFL;
 
+    private static final int[] P32 = new int[]{ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
+
     protected X448Field() {}
 
     public static void add(int[] x, int[] y, int[] z)
@@ -42,6 +47,11 @@
         int z0 = z[0], z1 = z[1], z2 = z[2], z3 = z[3], z4 = z[4], z5 = z[5], z6 = z[6], z7 = z[7];
         int z8 = z[8], z9 = z[9], z10 = z[10], z11 = z[11], z12 = z[12], z13 = z[13], z14 = z[14], z15 = z[15];
 
+        z1   += (z0 >>> 28); z0 &= M28;
+        z5   += (z4 >>> 28); z4 &= M28;
+        z9   += (z8 >>> 28); z8 &= M28;
+        z13  += (z12 >>> 28); z12 &= M28;
+
         z2   += (z1 >>> 28); z1 &= M28;
         z6   += (z5 >>> 28); z5 &= M28;
         z10  += (z9 >>> 28); z9 &= M28;
@@ -124,6 +134,12 @@
         }
     }
 
+    public static void decode(int[] x, int xOff, int[] z)
+    {
+        decode224(x, xOff, z, 0);
+        decode224(x, xOff + 7, z, 8);
+    }
+
     public static void decode(byte[] x, int xOff, int[] z)
     {
         decode56(x, xOff, z, 0);
@@ -136,6 +152,21 @@
         decode56(x, xOff + 49, z, 14);
     }
 
+    private static void decode224(int[] x, int xOff, int[] z, int zOff)
+    {
+        int x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3];
+        int x4 = x[xOff + 4], x5 = x[xOff + 5], x6 = x[xOff + 6];
+
+        z[zOff + 0] =  x0                    & M28;
+        z[zOff + 1] = (x0 >>> 28 | x1 <<  4) & M28;
+        z[zOff + 2] = (x1 >>> 24 | x2 <<  8) & M28;
+        z[zOff + 3] = (x2 >>> 20 | x3 << 12) & M28;
+        z[zOff + 4] = (x3 >>> 16 | x4 << 16) & M28;
+        z[zOff + 5] = (x4 >>> 12 | x5 << 20) & M28;
+        z[zOff + 6] = (x5 >>>  8 | x6 << 24) & M28;
+        z[zOff + 7] =  x6 >>>  4;
+    }
+
     private static int decode24(byte[] bs, int off)
     {
         int n = bs[  off] & 0xFF;
@@ -161,6 +192,12 @@
         z[zOff + 1] = (lo >>> 28) | (hi << 4);
     }
 
+    public static void encode(int[] x, int[] z, int zOff)
+    {
+        encode224(x, 0, z, zOff);
+        encode224(x, 8, z, zOff + 7);
+    }
+
     public static void encode(int[] x,  byte[] z , int zOff)
     {
         encode56(x, 0, z, zOff);
@@ -173,6 +210,20 @@
         encode56(x, 14, z, zOff + 49);
     }
 
+    private static void encode224(int[] x, int xOff, int[] is, int off)
+    {
+        int x0 = x[xOff + 0], x1 = x[xOff + 1], x2 = x[xOff + 2], x3 = x[xOff + 3];
+        int x4 = x[xOff + 4], x5 = x[xOff + 5], x6 = x[xOff + 6], x7 = x[xOff + 7];
+
+        is[off + 0] =  x0         | (x1 << 28);
+        is[off + 1] = (x1 >>>  4) | (x2 << 24);
+        is[off + 2] = (x2 >>>  8) | (x3 << 20);
+        is[off + 3] = (x3 >>> 12) | (x4 << 16);
+        is[off + 4] = (x4 >>> 16) | (x5 << 12);
+        is[off + 5] = (x5 >>> 20) | (x6 <<  8);
+        is[off + 6] = (x6 >>> 24) | (x7 <<  4);
+    }
+
     private static void encode24(int n, byte[] bs, int off)
     {
         bs[  off] = (byte)(n       );
@@ -197,14 +248,35 @@
 
     public static void inv(int[] x, int[] z)
     {
-        // z = x^(p-2) = x^(2^448 - 2^224 - 3)
-        // (223 1s) (1 0s) (222 1s) (1 0s) (1 1s)
-        // Addition chain: [1] 2 3 6 9 18 19 37 74 111 [222] [223]
+//        int[] t = create();
+//        powPm3d4(x, t);
+//        sqr(t, 2, t);
+//        mul(t, x, z);
 
         int[] t = create();
-        powPm3d4(x, t);
-        sqr(t, 2, t);
-        mul(t, x, z);
+        int[] u = new int[14];
+
+        copy(x, 0, t, 0);
+        normalize(t);
+        encode(t, u, 0);
+
+        Mod.modOddInverse(P32, u, u);
+
+        decode(u, 0, z);
+    }
+
+    public static void invVar(int[] x, int[] z)
+    {
+        int[] t = create();
+        int[] u = new int[14];
+
+        copy(x, 0, t, 0);
+        normalize(t);
+        encode(t, u, 0);
+
+        Mod.modOddInverseVar(P32, u, u);
+
+        decode(u, 0, z);
     }
 
     public static int isZero(int[] x)
@@ -646,16 +718,22 @@
         mul(t, x222, z);
     }
 
-    private static void reduce(int[] z, int c)
+    private static void reduce(int[] z, int x)
     {
         int t = z[15], z15 = t & M28;
-        t = (t >> 28) + c;
-        z[8] += t;
-        for (int i = 0; i < 15; ++i)
+        t = (t >>> 28) + x;
+
+        long cc = t;
+        for (int i = 0; i < 8; ++i)
         {
-            t += z[i]; z[i] = t & M28; t >>= 28;
+            cc += z[i] & U32; z[i] = (int)cc & M28; cc >>= 28;
         }
-        z[15] = z15 + t;
+        cc += t;
+        for (int i = 8; i < 15; ++i)
+        {
+            cc += z[i] & U32; z[i] = (int)cc & M28; cc >>= 28;
+        }
+        z[15] = z15 + (int)cc;
     }
 
     public static void sqr(int[] x, int[] z)
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
index 1fe5621..9ab2694 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
@@ -10,7 +10,6 @@
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.math.raw.Nat256;
 import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Strings;
 
 public abstract class Ed25519
 {
@@ -23,6 +22,9 @@
         public static final int Ed25519ph = 2;
     }
 
+    private static class F extends X25519Field {};
+
+    private static final long M08L = 0x000000FFL;
     private static final long M28L = 0x0FFFFFFFL;
     private static final long M32L = 0xFFFFFFFFL;
 
@@ -35,10 +37,15 @@
     public static final int SECRET_KEY_SIZE = 32;
     public static final int SIGNATURE_SIZE = POINT_BYTES + SCALAR_BYTES;
 
-    private static final byte[] DOM2_PREFIX = Strings.toByteArray("SigEd25519 no Ed25519 collisions");
+    // "SigEd25519 no Ed25519 collisions"
+    private static final byte[] DOM2_PREFIX = new byte[]{ 0x53, 0x69, 0x67, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39,
+        0x20, 0x6e, 0x6f, 0x20, 0x45, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x69, 0x73,
+        0x69, 0x6f, 0x6e, 0x73 };
 
-    private static final int[] P = new int[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF };
-    private static final int[] L = new int[]{ 0x5CF5D3ED, 0x5812631A, 0xA2F79CD6, 0x14DEF9DE, 0x00000000, 0x00000000, 0x00000000, 0x10000000 };
+    private static final int[] P = new int[]{ 0xFFFFFFED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+        0xFFFFFFFF, 0x7FFFFFFF };
+    private static final int[] L = new int[]{ 0x5CF5D3ED, 0x5812631A, 0xA2F79CD6, 0x14DEF9DE, 0x00000000, 0x00000000,
+        0x00000000, 0x10000000 };
 
     private static final int L0 = 0xFCF5D3ED;   // L0:26/--
     private static final int L1 = 0x012631A6;   // L1:24/22
@@ -47,7 +54,7 @@
     private static final int L4 = 0x000014DF;   // L4:12/11
 
     private static final int[] B_x = new int[]{ 0x0325D51A, 0x018B5823, 0x007B2C95, 0x0304A92D, 0x00D2598E, 0x01D6DC5C,
-        0x01388C7F, 0x013FEC0A, 0x029E6B72, 0x0042D26D };    
+        0x01388C7F, 0x013FEC0A, 0x029E6B72, 0x0042D26D };
     private static final int[] B_y = new int[]{ 0x02666658, 0x01999999, 0x00666666, 0x03333333, 0x00CCCCCC, 0x02666666,
         0x01999999, 0x00666666, 0x03333333, 0x00CCCCCC, };
     private static final int[] C_d = new int[]{ 0x035978A3, 0x02D37284, 0x018AB75E, 0x026A0A0E, 0x0000E014, 0x0379E898,
@@ -72,32 +79,32 @@
 
     private static class PointAccum
     {
-        int[] x = X25519Field.create();
-        int[] y = X25519Field.create();
-        int[] z = X25519Field.create();
-        int[] u = X25519Field.create();
-        int[] v = X25519Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
+        int[] z = F.create();
+        int[] u = F.create();
+        int[] v = F.create();
     }
 
     private static class PointAffine
     {
-        int[] x = X25519Field.create();
-        int[] y = X25519Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
     }
 
     private static class PointExt
     {
-        int[] x = X25519Field.create();
-        int[] y = X25519Field.create();
-        int[] z = X25519Field.create();
-        int[] t = X25519Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
+        int[] z = F.create();
+        int[] t = F.create();
     }
 
     private static class PointPrecomp
     {
-        int[] ypx_h = X25519Field.create();
-        int[] ymx_h = X25519Field.create();
-        int[] xyd = X25519Field.create();
+        int[] ypx_h = F.create();
+        int[] ymx_h = F.create();
+        int[] xyd = F.create();
     }
 
     private static byte[] calculateS(byte[] r, byte[] k, byte[] s)
@@ -124,42 +131,42 @@
 
     private static int checkPoint(int[] x, int[] y)
     {
-        int[] t = X25519Field.create();
-        int[] u = X25519Field.create();
-        int[] v = X25519Field.create();
+        int[] t = F.create();
+        int[] u = F.create();
+        int[] v = F.create();
 
-        X25519Field.sqr(x, u);
-        X25519Field.sqr(y, v);
-        X25519Field.mul(u, v, t);
-        X25519Field.sub(v, u, v);
-        X25519Field.mul(t, C_d, t);
-        X25519Field.addOne(t);
-        X25519Field.sub(t, v, t);
-        X25519Field.normalize(t);
+        F.sqr(x, u);
+        F.sqr(y, v);
+        F.mul(u, v, t);
+        F.sub(v, u, v);
+        F.mul(t, C_d, t);
+        F.addOne(t);
+        F.sub(t, v, t);
+        F.normalize(t);
 
-        return X25519Field.isZero(t);
+        return F.isZero(t);
     }
 
     private static int checkPoint(int[] x, int[] y, int[] z)
     {
-        int[] t = X25519Field.create();
-        int[] u = X25519Field.create();
-        int[] v = X25519Field.create();
-        int[] w = X25519Field.create();
+        int[] t = F.create();
+        int[] u = F.create();
+        int[] v = F.create();
+        int[] w = F.create();
 
-        X25519Field.sqr(x, u);
-        X25519Field.sqr(y, v);
-        X25519Field.sqr(z, w);
-        X25519Field.mul(u, v, t);
-        X25519Field.sub(v, u, v);
-        X25519Field.mul(v, w, v);
-        X25519Field.sqr(w, w);
-        X25519Field.mul(t, C_d, t);
-        X25519Field.add(t, w, t);
-        X25519Field.sub(t, v, t);
-        X25519Field.normalize(t);
+        F.sqr(x, u);
+        F.sqr(y, v);
+        F.sqr(z, w);
+        F.mul(u, v, t);
+        F.sub(v, u, v);
+        F.mul(v, w, v);
+        F.sqr(w, w);
+        F.mul(t, C_d, t);
+        F.add(t, w, t);
+        F.sub(t, v, t);
+        F.normalize(t);
 
-        return X25519Field.isZero(t);
+        return F.isZero(t);
     }
 
     private static boolean checkPointVar(byte[] p)
@@ -223,30 +230,30 @@
         int x_0 = (py[POINT_BYTES - 1] & 0x80) >>> 7;
         py[POINT_BYTES - 1] &= 0x7F;
 
-        X25519Field.decode(py, 0, r.y);
+        F.decode(py, 0, r.y);
 
-        int[] u = X25519Field.create();
-        int[] v = X25519Field.create();
+        int[] u = F.create();
+        int[] v = F.create();
 
-        X25519Field.sqr(r.y, u);
-        X25519Field.mul(C_d, u, v);
-        X25519Field.subOne(u);
-        X25519Field.addOne(v);
+        F.sqr(r.y, u);
+        F.mul(C_d, u, v);
+        F.subOne(u);
+        F.addOne(v);
 
-        if (!X25519Field.sqrtRatioVar(u, v, r.x))
+        if (!F.sqrtRatioVar(u, v, r.x))
         {
             return false;
         }
 
-        X25519Field.normalize(r.x);
-        if (x_0 == 1 && X25519Field.isZeroVar(r.x))
+        F.normalize(r.x);
+        if (x_0 == 1 && F.isZeroVar(r.x))
         {
             return false;
         }
 
         if (negate ^ (x_0 != (r.x[0] & 1)))
         {
-            X25519Field.negate(r.x, r.x);
+            F.negate(r.x, r.x);
         }
 
         return true;
@@ -261,10 +268,14 @@
     {
         if (ctx != null)
         {
-            d.update(DOM2_PREFIX, 0, DOM2_PREFIX.length);
-            d.update(phflag);
-            d.update((byte)ctx.length);
-            d.update(ctx, 0, ctx.length);
+            int n = DOM2_PREFIX.length;
+            byte[] t = new byte[n + 2 + ctx.length];
+            System.arraycopy(DOM2_PREFIX, 0, t, 0, n);
+            t[n] = phflag;
+            t[n + 1] = (byte)ctx.length;
+            System.arraycopy(ctx, 0, t, n + 2, ctx.length);
+
+            d.update(t, 0, t.length);
         }
     }
 
@@ -291,18 +302,18 @@
 
     private static int encodePoint(PointAccum p, byte[] r, int rOff)
     {
-        int[] x = X25519Field.create();
-        int[] y = X25519Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
 
-        X25519Field.inv(p.z, y);
-        X25519Field.mul(p.x, y, x);
-        X25519Field.mul(p.y, y, y);
-        X25519Field.normalize(x);
-        X25519Field.normalize(y);
+        F.inv(p.z, y);
+        F.mul(p.x, y, x);
+        F.mul(p.y, y, y);
+        F.normalize(x);
+        F.normalize(y);
 
         int result = checkPoint(x, y);
 
-        X25519Field.encode(y, r, rOff);
+        F.encode(y, r, rOff);
         r[rOff + POINT_BYTES - 1] |= ((x[0] & 1) << 7);
 
         return result;
@@ -333,7 +344,7 @@
         return (x[w] >>> b) & 15;
     }
 
-    private static byte[] getWNAF(int[] n, int width)
+    private static byte[] getWnafVar(int[] n, int width)
     {
 //        assert n[SCALAR_INTS - 1] >>> 28 == 0;
 
@@ -386,8 +397,8 @@
         return ws;
     }
 
-    private static void implSign(Digest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte phflag,
-        byte[] m, int mOff, int mLen, byte[] sig, int sigOff)
+    private static void implSign(Digest d, byte[] h, byte[] s, byte[] pk, int pkOff, byte[] ctx, byte phflag, byte[] m,
+        int mOff, int mLen, byte[] sig, int sigOff)
     {
         dom2(d, phflag, ctx);
         d.update(h, SCALAR_BYTES, SCALAR_BYTES);
@@ -454,8 +465,8 @@
         implSign(d, h, s, pk, pkOff, ctx, phflag, m, mOff, mLen, sig, sigOff);
     }
 
-    private static boolean implVerify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte phflag,
-        byte[] m, int mOff, int mLen)
+    private static boolean implVerify(byte[] sig, int sigOff, byte[] pk, int pkOff, byte[] ctx, byte phflag, byte[] m,
+        int mOff, int mLen)
     {
         if (!checkContextVar(ctx, phflag))
         {
@@ -506,174 +517,174 @@
 
     private static void pointAdd(PointExt p, PointAccum r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] D = X25519Field.create();
-        int[] E = r.u;
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = r.v;
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = r.u;
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = r.v;
 
-        X25519Field.apm(r.y, r.x, B, A);
-        X25519Field.apm(p.y, p.x, D, C);
-        X25519Field.mul(A, C, A);
-        X25519Field.mul(B, D, B);
-        X25519Field.mul(r.u, r.v, C);
-        X25519Field.mul(C, p.t, C);
-        X25519Field.mul(C, C_d2, C);
-        X25519Field.mul(r.z, p.z, D);
-        X25519Field.add(D, D, D);
-        X25519Field.apm(B, A, H, E);
-        X25519Field.apm(D, C, G, F);
-        X25519Field.carry(G);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
+        F.apm(r.y, r.x, b, a);
+        F.apm(p.y, p.x, d, c);
+        F.mul(a, c, a);
+        F.mul(b, d, b);
+        F.mul(r.u, r.v, c);
+        F.mul(c, p.t, c);
+        F.mul(c, C_d2, c);
+        F.mul(r.z, p.z, d);
+        F.add(d, d, d);
+        F.apm(b, a, h, e);
+        F.apm(d, c, g, f);
+        F.carry(g);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static void pointAdd(PointExt p, PointExt r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] D = X25519Field.create();
-        int[] E = X25519Field.create();
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = X25519Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = F.create();
 
-        X25519Field.apm(p.y, p.x, B, A);
-        X25519Field.apm(r.y, r.x, D, C);
-        X25519Field.mul(A, C, A);
-        X25519Field.mul(B, D, B);
-        X25519Field.mul(p.t, r.t, C);
-        X25519Field.mul(C, C_d2, C);
-        X25519Field.mul(p.z, r.z, D);
-        X25519Field.add(D, D, D);
-        X25519Field.apm(B, A, H, E);
-        X25519Field.apm(D, C, G, F);
-        X25519Field.carry(G);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
-        X25519Field.mul(E, H, r.t);
+        F.apm(p.y, p.x, b, a);
+        F.apm(r.y, r.x, d, c);
+        F.mul(a, c, a);
+        F.mul(b, d, b);
+        F.mul(p.t, r.t, c);
+        F.mul(c, C_d2, c);
+        F.mul(p.z, r.z, d);
+        F.add(d, d, d);
+        F.apm(b, a, h, e);
+        F.apm(d, c, g, f);
+        F.carry(g);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
+        F.mul(e, h, r.t);
     }
 
     private static void pointAddVar(boolean negate, PointExt p, PointAccum r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] D = X25519Field.create();
-        int[] E = r.u;
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = r.v;
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = r.u;
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = r.v;
 
-        int[] c, d, f, g;
+        int[] nc, nd, nf, ng;
         if (negate)
         {
-            c = D; d = C; f = G; g = F;
+            nc = d; nd = c; nf = g; ng = f;
         }
         else
         {
-            c = C; d = D; f = F; g = G;
+            nc = c; nd = d; nf = f; ng = g;
         }
 
-        X25519Field.apm(r.y, r.x, B, A);
-        X25519Field.apm(p.y, p.x, d, c);
-        X25519Field.mul(A, C, A);
-        X25519Field.mul(B, D, B);
-        X25519Field.mul(r.u, r.v, C);
-        X25519Field.mul(C, p.t, C);
-        X25519Field.mul(C, C_d2, C);
-        X25519Field.mul(r.z, p.z, D);
-        X25519Field.add(D, D, D);
-        X25519Field.apm(B, A, H, E);
-        X25519Field.apm(D, C, g, f);
-        X25519Field.carry(g);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
+        F.apm(r.y, r.x, b, a);
+        F.apm(p.y, p.x, nd, nc);
+        F.mul(a, c, a);
+        F.mul(b, d, b);
+        F.mul(r.u, r.v, c);
+        F.mul(c, p.t, c);
+        F.mul(c, C_d2, c);
+        F.mul(r.z, p.z, d);
+        F.add(d, d, d);
+        F.apm(b, a, h, e);
+        F.apm(d, c, ng, nf);
+        F.carry(ng);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static void pointAddVar(boolean negate, PointExt p, PointExt q, PointExt r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] D = X25519Field.create();
-        int[] E = X25519Field.create();
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = X25519Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = F.create();
 
-        int[] c, d, f, g;
+        int[] nc, nd, nf, ng;
         if (negate)
         {
-            c = D; d = C; f = G; g = F;
+            nc = d; nd = c; nf = g; ng = f;
         }
         else
         {
-            c = C; d = D; f = F; g = G;
+            nc = c; nd = d; nf = f; ng = g;
         }
 
-        X25519Field.apm(p.y, p.x, B, A);
-        X25519Field.apm(q.y, q.x, d, c);
-        X25519Field.mul(A, C, A);
-        X25519Field.mul(B, D, B);
-        X25519Field.mul(p.t, q.t, C);
-        X25519Field.mul(C, C_d2, C);
-        X25519Field.mul(p.z, q.z, D);
-        X25519Field.add(D, D, D);
-        X25519Field.apm(B, A, H, E);
-        X25519Field.apm(D, C, g, f);
-        X25519Field.carry(g);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
-        X25519Field.mul(E, H, r.t);
+        F.apm(p.y, p.x, b, a);
+        F.apm(q.y, q.x, nd, nc);
+        F.mul(a, c, a);
+        F.mul(b, d, b);
+        F.mul(p.t, q.t, c);
+        F.mul(c, C_d2, c);
+        F.mul(p.z, q.z, d);
+        F.add(d, d, d);
+        F.apm(b, a, h, e);
+        F.apm(d, c, ng, nf);
+        F.carry(ng);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
+        F.mul(e, h, r.t);
     }
 
     private static void pointAddPrecomp(PointPrecomp p, PointAccum r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] E = r.u;
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = r.v;
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] e = r.u;
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = r.v;
 
-        X25519Field.apm(r.y, r.x, B, A);
-        X25519Field.mul(A, p.ymx_h, A);
-        X25519Field.mul(B, p.ypx_h, B);
-        X25519Field.mul(r.u, r.v, C);
-        X25519Field.mul(C, p.xyd, C);
-        X25519Field.apm(B, A, H, E);
-        X25519Field.apm(r.z, C, G, F);
-        X25519Field.carry(G);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
+        F.apm(r.y, r.x, b, a);
+        F.mul(a, p.ymx_h, a);
+        F.mul(b, p.ypx_h, b);
+        F.mul(r.u, r.v, c);
+        F.mul(c, p.xyd, c);
+        F.apm(b, a, h, e);
+        F.apm(r.z, c, g, f);
+        F.carry(g);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static PointExt pointCopy(PointAccum p)
     {
         PointExt r = new PointExt();
-        X25519Field.copy(p.x, 0, r.x, 0);
-        X25519Field.copy(p.y, 0, r.y, 0);
-        X25519Field.copy(p.z, 0, r.z, 0);
-        X25519Field.mul(p.u, p.v, r.t);
+        F.copy(p.x, 0, r.x, 0);
+        F.copy(p.y, 0, r.y, 0);
+        F.copy(p.z, 0, r.z, 0);
+        F.mul(p.u, p.v, r.t);
         return r;
     }
 
     private static PointExt pointCopy(PointAffine p)
     {
         PointExt r = new PointExt();
-        X25519Field.copy(p.x, 0, r.x, 0);
-        X25519Field.copy(p.y, 0, r.y, 0);
+        F.copy(p.x, 0, r.x, 0);
+        F.copy(p.y, 0, r.y, 0);
         pointExtendXY(r);
         return r;
     }
@@ -687,55 +698,55 @@
 
     private static void pointCopy(PointAffine p, PointAccum r)
     {
-        X25519Field.copy(p.x, 0, r.x, 0);
-        X25519Field.copy(p.y, 0, r.y, 0);
+        F.copy(p.x, 0, r.x, 0);
+        F.copy(p.y, 0, r.y, 0);
         pointExtendXY(r);
     }
 
     private static void pointCopy(PointExt p, PointExt r)
     {
-        X25519Field.copy(p.x, 0, r.x, 0);
-        X25519Field.copy(p.y, 0, r.y, 0);
-        X25519Field.copy(p.z, 0, r.z, 0);
-        X25519Field.copy(p.t, 0, r.t, 0);
+        F.copy(p.x, 0, r.x, 0);
+        F.copy(p.y, 0, r.y, 0);
+        F.copy(p.z, 0, r.z, 0);
+        F.copy(p.t, 0, r.t, 0);
     }
 
     private static void pointDouble(PointAccum r)
     {
-        int[] A = X25519Field.create();
-        int[] B = X25519Field.create();
-        int[] C = X25519Field.create();
-        int[] E = r.u;
-        int[] F = X25519Field.create();
-        int[] G = X25519Field.create();
-        int[] H = r.v;
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] e = r.u;
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = r.v;
 
-        X25519Field.sqr(r.x, A);
-        X25519Field.sqr(r.y, B);
-        X25519Field.sqr(r.z, C);
-        X25519Field.add(C, C, C);
-        X25519Field.apm(A, B, H, G);
-        X25519Field.add(r.x, r.y, E);
-        X25519Field.sqr(E, E);
-        X25519Field.sub(H, E, E);
-        X25519Field.add(C, G, F);
-        X25519Field.carry(F);
-        X25519Field.mul(E, F, r.x);
-        X25519Field.mul(G, H, r.y);
-        X25519Field.mul(F, G, r.z);
+        F.sqr(r.x, a);
+        F.sqr(r.y, b);
+        F.sqr(r.z, c);
+        F.add(c, c, c);
+        F.apm(a, b, h, g);
+        F.add(r.x, r.y, e);
+        F.sqr(e, e);
+        F.sub(h, e, e);
+        F.add(c, g, f);
+        F.carry(f);
+        F.mul(e, f, r.x);
+        F.mul(g, h, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static void pointExtendXY(PointAccum p)
     {
-        X25519Field.one(p.z);
-        X25519Field.copy(p.x, 0, p.u, 0);
-        X25519Field.copy(p.y, 0, p.v, 0);
+        F.one(p.z);
+        F.copy(p.x, 0, p.u, 0);
+        F.copy(p.y, 0, p.v, 0);
     }
 
     private static void pointExtendXY(PointExt p)
     {
-        X25519Field.one(p.z);
-        X25519Field.mul(p.x, p.y, p.t);
+        F.one(p.z);
+        F.mul(p.x, p.y, p.t);
     }
 
     private static void pointLookup(int block, int index, PointPrecomp p)
@@ -743,51 +754,53 @@
 //        assert 0 <= block && block < PRECOMP_BLOCKS;
 //        assert 0 <= index && index < PRECOMP_POINTS;
 
-        int off = block * PRECOMP_POINTS * 3 * X25519Field.SIZE;
+        int off = block * PRECOMP_POINTS * 3 * F.SIZE;
 
         for (int i = 0; i < PRECOMP_POINTS; ++i)
         {
             int cond = ((i ^ index) - 1) >> 31;
-            X25519Field.cmov(cond, precompBase, off, p.ypx_h, 0);       off += X25519Field.SIZE;
-            X25519Field.cmov(cond, precompBase, off, p.ymx_h, 0);       off += X25519Field.SIZE;
-            X25519Field.cmov(cond, precompBase, off, p.xyd,   0);       off += X25519Field.SIZE;
+            F.cmov(cond, precompBase, off, p.ypx_h, 0);     off += F.SIZE;
+            F.cmov(cond, precompBase, off, p.ymx_h, 0);     off += F.SIZE;
+            F.cmov(cond, precompBase, off, p.xyd,   0);     off += F.SIZE;
         }
     }
 
     private static void pointLookup(int[] x, int n, int[] table, PointExt r)
     {
+        // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
+
         int w = getWindow4(x, n);
 
-        int sign = (w >>> (PRECOMP_TEETH - 1)) ^ 1;
-        int abs = (w ^ -sign) & PRECOMP_MASK;
+        int sign = (w >>> (4 - 1)) ^ 1;
+        int abs = (w ^ -sign) & 7;
 
 //        assert sign == 0 || sign == 1;
-//        assert 0 <= abs && abs < PRECOMP_POINTS;
+//        assert 0 <= abs && abs < 8;
 
-        for (int i = 0, off = 0; i < PRECOMP_POINTS; ++i)
+        for (int i = 0, off = 0; i < 8; ++i)
         {
             int cond = ((i ^ abs) - 1) >> 31;
-            X25519Field.cmov(cond, table, off, r.x, 0);     off += X25519Field.SIZE;
-            X25519Field.cmov(cond, table, off, r.y, 0);     off += X25519Field.SIZE;
-            X25519Field.cmov(cond, table, off, r.z, 0);     off += X25519Field.SIZE;
-            X25519Field.cmov(cond, table, off, r.t, 0);     off += X25519Field.SIZE;
+            F.cmov(cond, table, off, r.x, 0);       off += F.SIZE;
+            F.cmov(cond, table, off, r.y, 0);       off += F.SIZE;
+            F.cmov(cond, table, off, r.z, 0);       off += F.SIZE;
+            F.cmov(cond, table, off, r.t, 0);       off += F.SIZE;
         }
 
-        X25519Field.cnegate(sign, r.x);
-        X25519Field.cnegate(sign, r.t);
+        F.cnegate(sign, r.x);
+        F.cnegate(sign, r.t);
     }
 
     private static void pointLookup(int[] table, int index, PointExt r)
     {
-        int off = X25519Field.SIZE * 4 * index;
+        int off = F.SIZE * 4 * index;
 
-        X25519Field.copy(table, off, r.x, 0);       off += X25519Field.SIZE;
-        X25519Field.copy(table, off, r.y, 0);       off += X25519Field.SIZE;
-        X25519Field.copy(table, off, r.z, 0);       off += X25519Field.SIZE;
-        X25519Field.copy(table, off, r.t, 0);
+        F.copy(table, off, r.x, 0);     off += F.SIZE;
+        F.copy(table, off, r.y, 0);     off += F.SIZE;
+        F.copy(table, off, r.z, 0);     off += F.SIZE;
+        F.copy(table, off, r.t, 0);
     }
 
-    private static int[] pointPrecomp(PointAffine p, int count)
+    private static int[] pointPrecompute(PointAffine p, int count)
     {
 //        assert count > 0;
 
@@ -795,16 +808,16 @@
         PointExt d = pointCopy(q);
         pointAdd(q, d);
 
-        int[] table = X25519Field.createTable(count * 4);
+        int[] table = F.createTable(count * 4);
         int off = 0;
 
         int i = 0;
         for (;;)
         {
-            X25519Field.copy(q.x, 0, table, off);       off += X25519Field.SIZE;
-            X25519Field.copy(q.y, 0, table, off);       off += X25519Field.SIZE;
-            X25519Field.copy(q.z, 0, table, off);       off += X25519Field.SIZE;
-            X25519Field.copy(q.t, 0, table, off);       off += X25519Field.SIZE;
+            F.copy(q.x, 0, table, off);     off += F.SIZE;
+            F.copy(q.y, 0, table, off);     off += F.SIZE;
+            F.copy(q.z, 0, table, off);     off += F.SIZE;
+            F.copy(q.t, 0, table, off);     off += F.SIZE;
 
             if (++i == count)
             {
@@ -817,7 +830,7 @@
         return table;
     }
 
-    private static PointExt[] pointPrecompVar(PointExt p, int count)
+    private static PointExt[] pointPrecomputeVar(PointExt p, int count)
     {
 //        assert count > 0;
 
@@ -835,19 +848,19 @@
 
     private static void pointSetNeutral(PointAccum p)
     {
-        X25519Field.zero(p.x);
-        X25519Field.one(p.y);
-        X25519Field.one(p.z);
-        X25519Field.zero(p.u);
-        X25519Field.one(p.v);
+        F.zero(p.x);
+        F.one(p.y);
+        F.one(p.z);
+        F.zero(p.u);
+        F.one(p.v);
     }
 
     private static void pointSetNeutral(PointExt p)
     {
-        X25519Field.zero(p.x);
-        X25519Field.one(p.y);
-        X25519Field.one(p.z);
-        X25519Field.zero(p.t);
+        F.zero(p.x);
+        F.one(p.y);
+        F.one(p.z);
+        F.zero(p.t);
     }
 
     public static void precompute()
@@ -862,19 +875,19 @@
             // Precomputed table for the base point in verification ladder
             {
                 PointExt b = new PointExt();
-                X25519Field.copy(B_x, 0, b.x, 0);
-                X25519Field.copy(B_y, 0, b.y, 0);
+                F.copy(B_x, 0, b.x, 0);
+                F.copy(B_y, 0, b.y, 0);
                 pointExtendXY(b);
 
-                precompBaseTable = pointPrecompVar(b, 1 << (WNAF_WIDTH_BASE - 2));
+                precompBaseTable = pointPrecomputeVar(b, 1 << (WNAF_WIDTH_BASE - 2));
             }
 
             PointAccum p = new PointAccum();
-            X25519Field.copy(B_x, 0, p.x, 0);
-            X25519Field.copy(B_y, 0, p.y, 0);
+            F.copy(B_x, 0, p.x, 0);
+            F.copy(B_y, 0, p.y, 0);
             pointExtendXY(p);
 
-            precompBase = X25519Field.createTable(PRECOMP_BLOCKS * PRECOMP_POINTS * 3);
+            precompBase = F.createTable(PRECOMP_BLOCKS * PRECOMP_POINTS * 3);
 
             int off = 0;
             for (int b = 0; b < PRECOMP_BLOCKS; ++b)
@@ -916,31 +929,65 @@
 
 //                assert k == PRECOMP_POINTS;
 
+                int[] cs = F.createTable(PRECOMP_POINTS);
+
+                // TODO[ed25519] A single batch inversion across all blocks?
+                {
+                    int[] u = F.create();
+                    F.copy(points[0].z, 0, u, 0);
+                    F.copy(u, 0, cs, 0);
+
+                    int i = 0;
+                    while (++i < PRECOMP_POINTS)
+                    {
+                        F.mul(u, points[i].z, u);
+                        F.copy(u, 0, cs, i * F.SIZE);
+                    }
+
+                    F.add(u, u, u);
+                    F.invVar(u, u);
+                    --i;
+
+                    int[] t = F.create();
+
+                    while (i > 0)
+                    {
+                        int j = i--;
+                        F.copy(cs, i * F.SIZE, t, 0);
+                        F.mul(t, u, t);
+                        F.copy(t, 0, cs, j * F.SIZE);
+                        F.mul(u, points[j].z, u);
+                    }
+
+                    F.copy(u, 0, cs, 0);
+                }
+
                 for (int i = 0; i < PRECOMP_POINTS; ++i)
                 {
                     PointExt q = points[i];
 
-                    int[] x = X25519Field.create();
-                    int[] y = X25519Field.create();
+                    int[] x = F.create();
+                    int[] y = F.create();
 
-                    X25519Field.add(q.z, q.z, x);
-                    // TODO[ed25519] Batch inversion
-                    X25519Field.inv(x, y);
-                    X25519Field.mul(q.x, y, x);
-                    X25519Field.mul(q.y, y, y);
+//                    F.add(q.z, q.z, x);
+//                    F.invVar(x, y);
+                    F.copy(cs, i * F.SIZE, y, 0);
+
+                    F.mul(q.x, y, x);
+                    F.mul(q.y, y, y);
 
                     PointPrecomp r = new PointPrecomp();
-                    X25519Field.apm(y, x, r.ypx_h, r.ymx_h);
-                    X25519Field.mul(x, y, r.xyd);
-                    X25519Field.mul(r.xyd, C_d4, r.xyd);
+                    F.apm(y, x, r.ypx_h, r.ymx_h);
+                    F.mul(x, y, r.xyd);
+                    F.mul(r.xyd, C_d4, r.xyd);
 
-                    X25519Field.normalize(r.ypx_h);
-                    X25519Field.normalize(r.ymx_h);
-//                    X25519Field.normalize(r.xyd);
+                    F.normalize(r.ypx_h);
+                    F.normalize(r.ymx_h);
+//                    F.normalize(r.xyd);
 
-                    X25519Field.copy(r.ypx_h, 0, precompBase, off);    off += X25519Field.SIZE;
-                    X25519Field.copy(r.ymx_h, 0, precompBase, off);    off += X25519Field.SIZE;
-                    X25519Field.copy(r.xyd,   0, precompBase, off);    off += X25519Field.SIZE;
+                    F.copy(r.ypx_h, 0, precompBase, off);       off += F.SIZE;
+                    F.copy(r.ymx_h, 0, precompBase, off);       off += F.SIZE;
+                    F.copy(r.xyd,   0, precompBase, off);       off += F.SIZE;
                 }
             }
 
@@ -977,7 +1024,7 @@
         long x15 = (decode24(n, 53) << 4) & M32L;   // x15:28/--
         long x16 =  decode32(n, 56)       & M32L;   // x16:32/--
         long x17 = (decode24(n, 60) << 4) & M32L;   // x17:28/--
-        long x18 =  n[63]                 & 0xFFL;  // x18:08/--
+        long x18 =  n[63]                 & M08L;   // x18:08/--
         long t;
 
 //        x18 += (x17 >> 28); x17 &= M28L;
@@ -1095,8 +1142,6 @@
 
     private static void scalarMult(byte[] k, PointAffine p, PointAccum r)
     {
-        precompute();
-
         int[] n = new int[SCALAR_INTS];
         decodeScalar(k, 0, n);
 
@@ -1105,20 +1150,21 @@
 
         Nat.shiftDownBits(SCALAR_INTS, n, 3, 1);
 
-//        int c1 = Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);     assert c1 == 0;
-        Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);
-//        int c2 = Nat.shiftDownBit(SCALAR_INTS, n, 0);           assert c2 == (1 << 31);
-        Nat.shiftDownBit(SCALAR_INTS, n, 0);
+        // Recode the scalar into signed-digit form
+        {
+            //int c1 =
+            Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);      //assert c1 == 0;
+            //int c2 =           
+            Nat.shiftDownBit(SCALAR_INTS, n, 0);            //assert c2 == (1 << 31);
+        }
 
 //        assert 1 == n[SCALAR_INTS - 1] >>> 28;
 
-        pointCopy(p, r);
-
-        int[] table = pointPrecomp(p, 8);
-
+        int[] table = pointPrecompute(p, 8);
         PointExt q = new PointExt();
 
         // Replace first 4 doublings (2^4 * P) with 1 addition (P + 15 * P)
+        pointCopy(p, r);
         pointLookup(table, 7, q);
         pointAdd(q, r);
 
@@ -1145,17 +1191,15 @@
     {
         precompute();
 
-        pointSetNeutral(r);
-
         int[] n = new int[SCALAR_INTS];
         decodeScalar(k, 0, n);
 
         // Recode the scalar into signed-digit form, then group comb bits in each block
         {
-//            int c1 = Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);     assert c1 == 0;
-            Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);
-//            int c2 = Nat.shiftDownBit(SCALAR_INTS, n, 1);           assert c2 == (1 << 31);
-            Nat.shiftDownBit(SCALAR_INTS, n, 1);
+            //int c1 =
+            Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);      //assert c1 == 0;
+            //int c2 =
+            Nat.shiftDownBit(SCALAR_INTS, n, 1);            //assert c2 == (1 << 31);
 
             for (int i = 0; i < SCALAR_INTS; ++i)
             {
@@ -1165,6 +1209,8 @@
 
         PointPrecomp p = new PointPrecomp();
 
+        pointSetNeutral(r);
+
         int cOff = (PRECOMP_SPACING - 1) * PRECOMP_TEETH;
         for (;;)
         {
@@ -1179,8 +1225,8 @@
 
                 pointLookup(b, abs, p);
 
-                X25519Field.cswap(sign, p.ypx_h, p.ymx_h);
-                X25519Field.cnegate(sign, p.xyd);
+                F.cswap(sign, p.ypx_h, p.ymx_h);
+                F.cnegate(sign, p.xyd);
 
                 pointAddPrecomp(p, r);
             }
@@ -1223,8 +1269,8 @@
         {
             throw new IllegalStateException();
         }
-        X25519Field.copy(p.y, 0, y, 0);
-        X25519Field.copy(p.z, 0, z, 0);
+        F.copy(p.y, 0, y, 0);
+        F.copy(p.z, 0, z, 0);
     }
 
     private static void scalarMultStrausVar(int[] nb, int[] np, PointAffine p, PointAccum r)
@@ -1233,10 +1279,10 @@
 
         final int width = 5;
 
-        byte[] ws_b = getWNAF(nb, WNAF_WIDTH_BASE);
-        byte[] ws_p = getWNAF(np, width);
+        byte[] ws_b = getWnafVar(nb, WNAF_WIDTH_BASE);
+        byte[] ws_p = getWnafVar(np, width);
 
-        PointExt[] tp = pointPrecompVar(pointCopy(p), 1 << (width - 2));
+        PointExt[] tp = pointPrecomputeVar(pointCopy(p), 1 << (width - 2));
 
         pointSetNeutral(r);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
index 9013635..4f581d1 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
@@ -8,7 +8,6 @@
 import org.bouncycastle.math.ec.rfc7748.X448Field;
 import org.bouncycastle.math.raw.Nat;
 import org.bouncycastle.util.Arrays;
-import org.bouncycastle.util.Strings;
 
 public abstract class Ed448
 {
@@ -20,6 +19,8 @@
         public static final int Ed448ph = 1;
     }
 
+    private static class F extends X448Field {};
+
     private static final long M26L = 0x03FFFFFFL;
     private static final long M28L = 0x0FFFFFFFL;
     private static final long M32L = 0xFFFFFFFFL;
@@ -33,7 +34,8 @@
     public static final int SECRET_KEY_SIZE = 57;
     public static final int SIGNATURE_SIZE = POINT_BYTES + SCALAR_BYTES;
 
-    private static final byte[] DOM4_PREFIX = Strings.toByteArray("SigEd448");
+    // "SigEd448"
+    private static final byte[] DOM4_PREFIX = new byte[]{ 0x53, 0x69, 0x67, 0x45, 0x64, 0x34, 0x34, 0x38 };
 
     private static final int[] P = new int[] { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
         0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF };
@@ -79,15 +81,15 @@
 
     private static class PointExt
     {
-        int[] x = X448Field.create();
-        int[] y = X448Field.create();
-        int[] z = X448Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
+        int[] z = F.create();
     }
 
     private static class PointPrecomp
     {
-        int[] x = X448Field.create();
-        int[] y = X448Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
     }
 
     private static byte[] calculateS(byte[] r, byte[] k, byte[] s)
@@ -113,42 +115,42 @@
 
     private static int checkPoint(int[] x, int[] y)
     {
-        int[] t = X448Field.create();
-        int[] u = X448Field.create();
-        int[] v = X448Field.create();
+        int[] t = F.create();
+        int[] u = F.create();
+        int[] v = F.create();
 
-        X448Field.sqr(x, u);
-        X448Field.sqr(y, v);
-        X448Field.mul(u, v, t);
-        X448Field.add(u, v, u);
-        X448Field.mul(t, -C_d, t);
-        X448Field.subOne(t);
-        X448Field.add(t, u, t);
-        X448Field.normalize(t);
+        F.sqr(x, u);
+        F.sqr(y, v);
+        F.mul(u, v, t);
+        F.add(u, v, u);
+        F.mul(t, -C_d, t);
+        F.subOne(t);
+        F.add(t, u, t);
+        F.normalize(t);
 
-        return X448Field.isZero(t);
+        return F.isZero(t);
     }
 
     private static int checkPoint(int[] x, int[] y, int[] z)
     {
-        int[] t = X448Field.create();
-        int[] u = X448Field.create();
-        int[] v = X448Field.create();
-        int[] w = X448Field.create();
+        int[] t = F.create();
+        int[] u = F.create();
+        int[] v = F.create();
+        int[] w = F.create();
 
-        X448Field.sqr(x, u);
-        X448Field.sqr(y, v);
-        X448Field.sqr(z, w);
-        X448Field.mul(u, v, t);
-        X448Field.add(u, v, u);
-        X448Field.mul(u, w, u);
-        X448Field.sqr(w, w);
-        X448Field.mul(t, -C_d, t);
-        X448Field.sub(t, w, t);
-        X448Field.add(t, u, t);
-        X448Field.normalize(t);
+        F.sqr(x, u);
+        F.sqr(y, v);
+        F.sqr(z, w);
+        F.mul(u, v, t);
+        F.add(u, v, u);
+        F.mul(u, w, u);
+        F.sqr(w, w);
+        F.mul(t, -C_d, t);
+        F.sub(t, w, t);
+        F.add(t, u, t);
+        F.normalize(t);
 
-        return X448Field.isZero(t);
+        return F.isZero(t);
     }
 
     private static boolean checkPointVar(byte[] p)
@@ -228,31 +230,31 @@
         int x_0 = (py[POINT_BYTES - 1] & 0x80) >>> 7;
         py[POINT_BYTES - 1] &= 0x7F;
 
-        X448Field.decode(py, 0, r.y);
+        F.decode(py, 0, r.y);
 
-        int[] u = X448Field.create();
-        int[] v = X448Field.create();
+        int[] u = F.create();
+        int[] v = F.create();
 
-        X448Field.sqr(r.y, u);
-        X448Field.mul(u, -C_d, v);
-        X448Field.negate(u, u);
-        X448Field.addOne(u);
-        X448Field.addOne(v);
+        F.sqr(r.y, u);
+        F.mul(u, -C_d, v);
+        F.negate(u, u);
+        F.addOne(u);
+        F.addOne(v);
 
-        if (!X448Field.sqrtRatioVar(u, v, r.x))
+        if (!F.sqrtRatioVar(u, v, r.x))
         {
             return false;
         }
 
-        X448Field.normalize(r.x);
-        if (x_0 == 1 && X448Field.isZeroVar(r.x))
+        F.normalize(r.x);
+        if (x_0 == 1 && F.isZeroVar(r.x))
         {
             return false;
         }
 
         if (negate ^ (x_0 != (r.x[0] & 1)))
         {
-            X448Field.negate(r.x, r.x);
+            F.negate(r.x, r.x);
         }
 
         pointExtendXY(r);
@@ -266,12 +268,16 @@
         decode32(k, kOff, n, 0, SCALAR_INTS);
     }
 
-    private static void dom4(Xof d, byte x, byte[] y)
+    private static void dom4(Xof d, byte phflag, byte[] ctx)
     {
-        d.update(DOM4_PREFIX, 0, DOM4_PREFIX.length);
-        d.update(x);
-        d.update((byte)y.length);
-        d.update(y, 0, y.length);
+        int n = DOM4_PREFIX.length;
+        byte[] t = new byte[n + 2 + ctx.length];
+        System.arraycopy(DOM4_PREFIX, 0, t, 0, n);
+        t[n] = phflag;
+        t[n + 1] = (byte)ctx.length;
+        System.arraycopy(ctx, 0, t, n + 2, ctx.length);
+
+        d.update(t, 0, t.length);
     }
 
     private static void encode24(int n, byte[] bs, int off)
@@ -297,18 +303,18 @@
 
     private static int encodePoint(PointExt p, byte[] r, int rOff)
     {
-        int[] x = X448Field.create();
-        int[] y = X448Field.create();
+        int[] x = F.create();
+        int[] y = F.create();
 
-        X448Field.inv(p.z, y);
-        X448Field.mul(p.x, y, x);
-        X448Field.mul(p.y, y, y);
-        X448Field.normalize(x);
-        X448Field.normalize(y);
+        F.inv(p.z, y);
+        F.mul(p.x, y, x);
+        F.mul(p.y, y, y);
+        F.normalize(x);
+        F.normalize(y);
 
         int result = checkPoint(x, y);
 
-        X448Field.encode(y, r, rOff);
+        F.encode(y, r, rOff);
         r[rOff + POINT_BYTES - 1] = (byte)((x[0] & 1) << 7);
 
         return result;
@@ -339,7 +345,7 @@
         return (x[w] >>> b) & 15;
     }
 
-    private static byte[] getWNAF(int[] n, int width)
+    private static byte[] getWnafVar(int[] n, int width)
     {
 //        assert n[SCALAR_INTS - 1] >>> 30 == 0;
 
@@ -512,116 +518,116 @@
 
     private static void pointAdd(PointExt p, PointExt r)
     {
-        int[] A = X448Field.create();
-        int[] B = X448Field.create();
-        int[] C = X448Field.create();
-        int[] D = X448Field.create();
-        int[] E = X448Field.create();
-        int[] F = X448Field.create();
-        int[] G = X448Field.create();
-        int[] H = X448Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = F.create();
 
-        X448Field.mul(p.z, r.z, A);
-        X448Field.sqr(A, B);
-        X448Field.mul(p.x, r.x, C);
-        X448Field.mul(p.y, r.y, D);
-        X448Field.mul(C, D, E);
-        X448Field.mul(E, -C_d, E);
-//        X448Field.apm(B, E, F, G);
-        X448Field.add(B, E, F);
-        X448Field.sub(B, E, G);
-        X448Field.add(p.x, p.y, B);
-        X448Field.add(r.x, r.y, E);
-        X448Field.mul(B, E, H);
-//        X448Field.apm(D, C, B, E);
-        X448Field.add(D, C, B);
-        X448Field.sub(D, C, E);
-        X448Field.carry(B);
-        X448Field.sub(H, B, H);
-        X448Field.mul(H, A, H);
-        X448Field.mul(E, A, E);
-        X448Field.mul(F, H, r.x);
-        X448Field.mul(E, G, r.y);
-        X448Field.mul(F, G, r.z);
+        F.mul(p.z, r.z, a);
+        F.sqr(a, b);
+        F.mul(p.x, r.x, c);
+        F.mul(p.y, r.y, d);
+        F.mul(c, d, e);
+        F.mul(e, -C_d, e);
+//        F.apm(b, e, f, g);
+        F.add(b, e, f);
+        F.sub(b, e, g);
+        F.add(p.x, p.y, b);
+        F.add(r.x, r.y, e);
+        F.mul(b, e, h);
+//        F.apm(d, c, b, e);
+        F.add(d, c, b);
+        F.sub(d, c, e);
+        F.carry(b);
+        F.sub(h, b, h);
+        F.mul(h, a, h);
+        F.mul(e, a, e);
+        F.mul(f, h, r.x);
+        F.mul(e, g, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static void pointAddVar(boolean negate, PointExt p, PointExt r)
     {
-        int[] A = X448Field.create();
-        int[] B = X448Field.create();
-        int[] C = X448Field.create();
-        int[] D = X448Field.create();
-        int[] E = X448Field.create();
-        int[] F = X448Field.create();
-        int[] G = X448Field.create();
-        int[] H = X448Field.create();
+        int[] a = F.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = F.create();
 
-        int[] b, e, f, g;
+        int[] nb, ne, nf, ng;
         if (negate)
         {
-            b = E; e = B; f = G; g = F;
-            X448Field.sub(p.y, p.x, H);
+            nb = e; ne = b; nf = g; ng = f;
+            F.sub(p.y, p.x, h);
         }
         else
         {
-            b = B; e = E; f = F; g = G;
-            X448Field.add(p.y, p.x, H);
+            nb = b; ne = e; nf = f; ng = g;
+            F.add(p.y, p.x, h);
         }
 
-        X448Field.mul(p.z, r.z, A);
-        X448Field.sqr(A, B);
-        X448Field.mul(p.x, r.x, C);
-        X448Field.mul(p.y, r.y, D);
-        X448Field.mul(C, D, E);
-        X448Field.mul(E, -C_d, E);
-//        X448Field.apm(B, E, F, G);
-        X448Field.add(B, E, f);
-        X448Field.sub(B, E, g);
-        X448Field.add(r.x, r.y, E);
-        X448Field.mul(H, E, H);
-//        X448Field.apm(D, C, B, E);
-        X448Field.add(D, C, b);
-        X448Field.sub(D, C, e);
-        X448Field.carry(b);
-        X448Field.sub(H, B, H);
-        X448Field.mul(H, A, H);
-        X448Field.mul(E, A, E);
-        X448Field.mul(F, H, r.x);
-        X448Field.mul(E, G, r.y);
-        X448Field.mul(F, G, r.z);
+        F.mul(p.z, r.z, a);
+        F.sqr(a, b);
+        F.mul(p.x, r.x, c);
+        F.mul(p.y, r.y, d);
+        F.mul(c, d, e);
+        F.mul(e, -C_d, e);
+//        F.apm(b, e, f, g);
+        F.add(b, e, nf);
+        F.sub(b, e, ng);
+        F.add(r.x, r.y, e);
+        F.mul(h, e, h);
+//        F.apm(d, c, b, e);
+        F.add(d, c, nb);
+        F.sub(d, c, ne);
+        F.carry(nb);
+        F.sub(h, b, h);
+        F.mul(h, a, h);
+        F.mul(e, a, e);
+        F.mul(f, h, r.x);
+        F.mul(e, g, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static void pointAddPrecomp(PointPrecomp p, PointExt r)
     {
-        int[] B = X448Field.create();
-        int[] C = X448Field.create();
-        int[] D = X448Field.create();
-        int[] E = X448Field.create();
-        int[] F = X448Field.create();
-        int[] G = X448Field.create();
-        int[] H = X448Field.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] f = F.create();
+        int[] g = F.create();
+        int[] h = F.create();
 
-        X448Field.sqr(r.z, B);
-        X448Field.mul(p.x, r.x, C);
-        X448Field.mul(p.y, r.y, D);
-        X448Field.mul(C, D, E);
-        X448Field.mul(E, -C_d, E);
-//        X448Field.apm(B, E, F, G);
-        X448Field.add(B, E, F);
-        X448Field.sub(B, E, G);
-        X448Field.add(p.x, p.y, B);
-        X448Field.add(r.x, r.y, E);
-        X448Field.mul(B, E, H);
-//        X448Field.apm(D, C, B, E);
-        X448Field.add(D, C, B);
-        X448Field.sub(D, C, E);
-        X448Field.carry(B);
-        X448Field.sub(H, B, H);
-        X448Field.mul(H, r.z, H);
-        X448Field.mul(E, r.z, E);
-        X448Field.mul(F, H, r.x);
-        X448Field.mul(E, G, r.y);
-        X448Field.mul(F, G, r.z);
+        F.sqr(r.z, b);
+        F.mul(p.x, r.x, c);
+        F.mul(p.y, r.y, d);
+        F.mul(c, d, e);
+        F.mul(e, -C_d, e);
+//        F.apm(b, e, f, g);
+        F.add(b, e, f);
+        F.sub(b, e, g);
+        F.add(p.x, p.y, b);
+        F.add(r.x, r.y, e);
+        F.mul(b, e, h);
+//        F.apm(d, c, b, e);
+        F.add(d, c, b);
+        F.sub(d, c, e);
+        F.carry(b);
+        F.sub(h, b, h);
+        F.mul(h, r.z, h);
+        F.mul(e, r.z, e);
+        F.mul(f, h, r.x);
+        F.mul(e, g, r.y);
+        F.mul(f, g, r.z);
     }
 
     private static PointExt pointCopy(PointExt p)
@@ -633,40 +639,40 @@
 
     private static void pointCopy(PointExt p, PointExt r)
     {
-        X448Field.copy(p.x, 0, r.x, 0);
-        X448Field.copy(p.y, 0, r.y, 0);
-        X448Field.copy(p.z, 0, r.z, 0);
+        F.copy(p.x, 0, r.x, 0);
+        F.copy(p.y, 0, r.y, 0);
+        F.copy(p.z, 0, r.z, 0);
     }
 
     private static void pointDouble(PointExt r)
     {
-        int[] B = X448Field.create();
-        int[] C = X448Field.create();
-        int[] D = X448Field.create();
-        int[] E = X448Field.create();
-        int[] H = X448Field.create();
-        int[] J = X448Field.create();
+        int[] b = F.create();
+        int[] c = F.create();
+        int[] d = F.create();
+        int[] e = F.create();
+        int[] h = F.create();
+        int[] j = F.create();
 
-        X448Field.add(r.x, r.y, B);
-        X448Field.sqr(B, B);
-        X448Field.sqr(r.x, C);
-        X448Field.sqr(r.y, D);
-        X448Field.add(C, D, E);
-        X448Field.carry(E);
-        X448Field.sqr(r.z, H);
-        X448Field.add(H, H, H);
-        X448Field.carry(H);
-        X448Field.sub(E, H, J);
-        X448Field.sub(B, E, B);
-        X448Field.sub(C, D, C);
-        X448Field.mul(B, J, r.x);
-        X448Field.mul(E, C, r.y);
-        X448Field.mul(E, J, r.z);
+        F.add(r.x, r.y, b);
+        F.sqr(b, b);
+        F.sqr(r.x, c);
+        F.sqr(r.y, d);
+        F.add(c, d, e);
+        F.carry(e);
+        F.sqr(r.z, h);
+        F.add(h, h, h);
+        F.carry(h);
+        F.sub(e, h, j);
+        F.sub(b, e, b);
+        F.sub(c, d, c);
+        F.mul(b, j, r.x);
+        F.mul(e, c, r.y);
+        F.mul(e, j, r.z);
     }
 
     private static void pointExtendXY(PointExt p)
     {
-        X448Field.one(p.z);
+        F.one(p.z);
     }
 
     private static void pointLookup(int block, int index, PointPrecomp p)
@@ -674,18 +680,20 @@
 //        assert 0 <= block && block < PRECOMP_BLOCKS;
 //        assert 0 <= index && index < PRECOMP_POINTS;
 
-        int off = block * PRECOMP_POINTS * 2 * X448Field.SIZE;
+        int off = block * PRECOMP_POINTS * 2 * F.SIZE;
 
         for (int i = 0; i < PRECOMP_POINTS; ++i)
         {
             int cond = ((i ^ index) - 1) >> 31;
-            X448Field.cmov(cond, precompBase, off, p.x, 0);     off += X448Field.SIZE;
-            X448Field.cmov(cond, precompBase, off, p.y, 0);     off += X448Field.SIZE;
+            F.cmov(cond, precompBase, off, p.x, 0);     off += F.SIZE;
+            F.cmov(cond, precompBase, off, p.y, 0);     off += F.SIZE;
         }
     }
 
     private static void pointLookup(int[] x, int n, int[] table, PointExt r)
     {
+        // TODO This method is currently hardcoded to 4-bit windows and 8 precomputed points
+
         int w = getWindow4(x, n);
 
         int sign = (w >>> (4 - 1)) ^ 1;
@@ -697,15 +705,15 @@
         for (int i = 0, off = 0; i < 8; ++i)
         {
             int cond = ((i ^ abs) - 1) >> 31;
-            X448Field.cmov(cond, table, off, r.x, 0);       off += X448Field.SIZE;
-            X448Field.cmov(cond, table, off, r.y, 0);       off += X448Field.SIZE;
-            X448Field.cmov(cond, table, off, r.z, 0);       off += X448Field.SIZE;
+            F.cmov(cond, table, off, r.x, 0);       off += F.SIZE;
+            F.cmov(cond, table, off, r.y, 0);       off += F.SIZE;
+            F.cmov(cond, table, off, r.z, 0);       off += F.SIZE;
         }
 
-        X448Field.cnegate(sign, r.x);
+        F.cnegate(sign, r.x);
     }
 
-    private static int[] pointPrecomp(PointExt p, int count)
+    private static int[] pointPrecompute(PointExt p, int count)
     {
 //        assert count > 0;
 
@@ -713,15 +721,15 @@
         PointExt d = pointCopy(q);
         pointDouble(d);
 
-        int[] table = X448Field.createTable(count * 3);
+        int[] table = F.createTable(count * 3);
         int off = 0;
 
         int i = 0;
         for (;;)
         {
-            X448Field.copy(q.x, 0, table, off);     off += X448Field.SIZE;
-            X448Field.copy(q.y, 0, table, off);     off += X448Field.SIZE;
-            X448Field.copy(q.z, 0, table, off);     off += X448Field.SIZE;
+            F.copy(q.x, 0, table, off);     off += F.SIZE;
+            F.copy(q.y, 0, table, off);     off += F.SIZE;
+            F.copy(q.z, 0, table, off);     off += F.SIZE;
 
             if (++i == count)
             {
@@ -734,7 +742,7 @@
         return table;
     }
 
-    private static PointExt[] pointPrecompVar(PointExt p, int count)
+    private static PointExt[] pointPrecomputeVar(PointExt p, int count)
     {
 //        assert count > 0;
 
@@ -753,9 +761,9 @@
 
     private static void pointSetNeutral(PointExt p)
     {
-        X448Field.zero(p.x);
-        X448Field.one(p.y);
-        X448Field.one(p.z);
+        F.zero(p.x);
+        F.one(p.y);
+        F.one(p.z);
     }
 
     public static void precompute()
@@ -768,13 +776,13 @@
             }
 
             PointExt p = new PointExt();
-            X448Field.copy(B_x, 0, p.x, 0);
-            X448Field.copy(B_y, 0, p.y, 0);
+            F.copy(B_x, 0, p.x, 0);
+            F.copy(B_y, 0, p.y, 0);
             pointExtendXY(p);
 
-            precompBaseTable = pointPrecompVar(p, 1 << (WNAF_WIDTH_BASE - 2));
+            precompBaseTable = pointPrecomputeVar(p, 1 << (WNAF_WIDTH_BASE - 2));
 
-            precompBase = X448Field.createTable(PRECOMP_BLOCKS * PRECOMP_POINTS * 2);
+            precompBase = F.createTable(PRECOMP_BLOCKS * PRECOMP_POINTS * 2);
 
             int off = 0;
             for (int b = 0; b < PRECOMP_BLOCKS; ++b)
@@ -816,19 +824,53 @@
 
 //                assert k == PRECOMP_POINTS;
 
+                int[] cs = F.createTable(PRECOMP_POINTS);
+
+                // TODO[ed448] A single batch inversion across all blocks?
+                {
+                    int[] u = F.create();
+                    F.copy(points[0].z, 0, u, 0);
+                    F.copy(u, 0, cs, 0);
+
+                    int i = 0;
+                    while (++i < PRECOMP_POINTS)
+                    {
+                        F.mul(u, points[i].z, u);
+                        F.copy(u, 0, cs, i * F.SIZE);
+                    }
+
+                    F.invVar(u, u);
+                    --i;
+
+                    int[] t = F.create();
+
+                    while (i > 0)
+                    {
+                        int j = i--;
+                        F.copy(cs, i * F.SIZE, t, 0);
+                        F.mul(t, u, t);
+                        F.copy(t, 0, cs, j * F.SIZE);
+                        F.mul(u, points[j].z, u);
+                    }
+
+                    F.copy(u, 0, cs, 0);
+                }
+
                 for (int i = 0; i < PRECOMP_POINTS; ++i)
                 {
                     PointExt q = points[i];
-                    // TODO[ed448] Batch inversion
-                    X448Field.inv(q.z, q.z);
-                    X448Field.mul(q.x, q.z, q.x);
-                    X448Field.mul(q.y, q.z, q.y);
 
-//                    X448Field.normalize(q.x);
-//                    X448Field.normalize(q.y);
+//                    F.invVar(q.z, q.z);
+                    F.copy(cs, i * F.SIZE, q.z, 0);
 
-                    X448Field.copy(q.x, 0, precompBase, off);   off += X448Field.SIZE;
-                    X448Field.copy(q.y, 0, precompBase, off);   off += X448Field.SIZE;
+                    F.mul(q.x, q.z, q.x);
+                    F.mul(q.y, q.z, q.y);
+
+//                    F.normalize(q.x);
+//                    F.normalize(q.y);
+
+                    F.copy(q.x, 0, precompBase, off);   off += F.SIZE;
+                    F.copy(q.y, 0, precompBase, off);   off += F.SIZE;
                 }
             }
 
@@ -1124,8 +1166,6 @@
 
     private static void scalarMult(byte[] k, PointExt p, PointExt r)
     {
-        precompute();
-
         int[] n = new int[SCALAR_INTS];
         decodeScalar(k, 0, n);
 
@@ -1136,17 +1176,17 @@
 
         // Recode the scalar into signed-digit form
         {
-//            int c1 = Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);     assert c1 == 0;
-            Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);
-//            int c2 = Nat.shiftDownBit(SCALAR_INTS, n, 1);           assert c2 == (1 << 31);
-            Nat.shiftDownBit(SCALAR_INTS, n, 1);
+            //int c1 =
+            Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);      //assert c1 == 0;
+            //int c2 =
+            Nat.shiftDownBit(SCALAR_INTS, n, 1);            //assert c2 == (1 << 31);
         }
 
-        int[] table = pointPrecomp(p, 8);
+        int[] table = pointPrecompute(p, 8);
+        PointExt q = new PointExt();
 
         pointLookup(n, 111, table, r);
 
-        PointExt q = new PointExt();
         for (int w = 110; w >= 0; --w)
         {
             for (int i = 0; i < 4; ++i)
@@ -1168,20 +1208,21 @@
     {
         precompute();
 
-        pointSetNeutral(r);
-
         int[] n = new int[SCALAR_INTS + 1];
         decodeScalar(k, 0, n);
 
         // Recode the scalar into signed-digit form
         {
             n[SCALAR_INTS] = 4 + Nat.cadd(SCALAR_INTS, ~n[0] & 1, n, L, n);
-//            int c = Nat.shiftDownBit(n.length, n, 0);                           assert c == (1 << 31);
+            //int c =
             Nat.shiftDownBit(n.length, n, 0);
+            //assert c == (1 << 31);
         }
 
         PointPrecomp p = new PointPrecomp();
 
+        pointSetNeutral(r);
+
         int cOff = PRECOMP_SPACING - 1;
         for (;;)
         {
@@ -1206,7 +1247,7 @@
 
                 pointLookup(b, abs, p);
 
-                X448Field.cnegate(sign, p.x);
+                F.cnegate(sign, p.x);
 
                 pointAddPrecomp(p, r);
             }
@@ -1249,8 +1290,8 @@
         {
             throw new IllegalStateException();
         }
-        X448Field.copy(p.x, 0, x, 0);
-        X448Field.copy(p.y, 0, y, 0);
+        F.copy(p.x, 0, x, 0);
+        F.copy(p.y, 0, y, 0);
     }
 
     private static void scalarMultStrausVar(int[] nb, int[] np, PointExt p, PointExt r)
@@ -1259,10 +1300,10 @@
 
         final int width = 5;
 
-        byte[] ws_b = getWNAF(nb, WNAF_WIDTH_BASE);
-        byte[] ws_p = getWNAF(np, width);
+        byte[] ws_b = getWnafVar(nb, WNAF_WIDTH_BASE);
+        byte[] ws_p = getWnafVar(np, width);
 
-        PointExt[] tp = pointPrecompVar(p, 1 << (width - 2));
+        PointExt[] tp = pointPrecomputeVar(p, 1 << (width - 2));
 
         pointSetNeutral(r);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/field/FiniteFields.java b/bcprov/src/main/java/org/bouncycastle/math/field/FiniteFields.java
index 7197ffd..c71eda7 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/field/FiniteFields.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/field/FiniteFields.java
@@ -17,7 +17,7 @@
         {
             if (exponents[i] <= exponents[i - 1])
             {
-                throw new IllegalArgumentException("Polynomial exponents must be montonically increasing");
+                throw new IllegalArgumentException("Polynomial exponents must be monotonically increasing");
             }
         }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Bits.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Bits.java
new file mode 100644
index 0000000..322a73c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Bits.java
@@ -0,0 +1,26 @@
+package org.bouncycastle.math.raw;
+
+public abstract class Bits
+{
+    public static int bitPermuteStep(int x, int m, int s)
+    {
+        int t = (x ^ (x >>> s)) & m;
+        return  (t ^ (t <<  s)) ^ x;
+    }
+
+    public static long bitPermuteStep(long x, long m, int s)
+    {
+        long t = (x ^ (x >>> s)) & m;
+        return   (t ^ (t <<  s)) ^ x;
+    }
+
+    public static int bitPermuteStepSimple(int x, int m, int s)
+    {
+        return ((x & m) << s) | ((x >>> s) & m);
+    }
+
+    public static long bitPermuteStepSimple(long x, long m, int s)
+    {
+        return ((x & m) << s) | ((x >>> s) & m);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Interleave.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Interleave.java
index 85f4f6d..0f693a5 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Interleave.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Interleave.java
@@ -70,11 +70,10 @@
     public static long expand32to64(int x)
     {
         // "shuffle" low half to even bits and high half to odd bits
-        int t;
-        t = (x ^ (x >>>  8)) & 0x0000FF00; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F0; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  1)) & 0x22222222; x ^= (t ^ (t <<  1));
+        x = Bits.bitPermuteStep(x, 0x0000FF00, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F0, 4);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C, 2);
+        x = Bits.bitPermuteStep(x, 0x22222222, 1);
 
         return ((x >>> 1) & M32) << 32 | (x & M32);
     }
@@ -82,26 +81,33 @@
     public static void expand64To128(long x, long[] z, int zOff)
     {
         // "shuffle" low half to even bits and high half to odd bits
-        long t;
-        t = (x ^ (x >>> 16)) & 0x00000000FFFF0000L; x ^= (t ^ (t << 16));
-        t = (x ^ (x >>>  8)) & 0x0000FF000000FF00L; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F000F000F0L; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C0C0C0C0CL; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  1)) & 0x2222222222222222L; x ^= (t ^ (t <<  1));
+        x = Bits.bitPermuteStep(x, 0x00000000FFFF0000L, 16);
+        x = Bits.bitPermuteStep(x, 0x0000FF000000FF00L, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F000F000F0L, 4);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C0C0C0C0CL, 2);
+        x = Bits.bitPermuteStep(x, 0x2222222222222222L, 1);
 
         z[zOff    ] = (x      ) & M64;
         z[zOff + 1] = (x >>> 1) & M64;
     }
 
+    public static void expand64To128(long[] xs, int xsOff, int xsLen, long[] zs, int zsOff)
+    {
+        for (int i = 0; i < xsLen; ++i)
+        {
+            expand64To128(xs[xsOff + i], zs, zsOff);
+            zsOff += 2;
+        }
+    }
+
     public static void expand64To128Rev(long x, long[] z, int zOff)
     {
         // "shuffle" low half to even bits and high half to odd bits
-        long t;
-        t = (x ^ (x >>> 16)) & 0x00000000FFFF0000L; x ^= (t ^ (t << 16));
-        t = (x ^ (x >>>  8)) & 0x0000FF000000FF00L; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F000F000F0L; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C0C0C0C0CL; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  1)) & 0x2222222222222222L; x ^= (t ^ (t <<  1));
+        x = Bits.bitPermuteStep(x, 0x00000000FFFF0000L, 16);
+        x = Bits.bitPermuteStep(x, 0x0000FF000000FF00L, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F000F000F0L, 4);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C0C0C0C0CL, 2);
+        x = Bits.bitPermuteStep(x, 0x2222222222222222L, 1);
 
         z[zOff    ] = (x     ) & M64R;
         z[zOff + 1] = (x << 1) & M64R;
@@ -110,68 +116,97 @@
     public static int shuffle(int x)
     {
         // "shuffle" low half to even bits and high half to odd bits
-        int t;
-        t = (x ^ (x >>>  8)) & 0x0000FF00; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F0; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  1)) & 0x22222222; x ^= (t ^ (t <<  1));
+        x = Bits.bitPermuteStep(x, 0x0000FF00, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F0, 4);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C, 2);
+        x = Bits.bitPermuteStep(x, 0x22222222, 1);
         return x;
     }
 
     public static long shuffle(long x)
     {
         // "shuffle" low half to even bits and high half to odd bits
-        long t;
-        t = (x ^ (x >>> 16)) & 0x00000000FFFF0000L; x ^= (t ^ (t << 16));
-        t = (x ^ (x >>>  8)) & 0x0000FF000000FF00L; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F000F000F0L; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C0C0C0C0CL; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  1)) & 0x2222222222222222L; x ^= (t ^ (t <<  1));
+        x = Bits.bitPermuteStep(x, 0x00000000FFFF0000L, 16);
+        x = Bits.bitPermuteStep(x, 0x0000FF000000FF00L, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F000F000F0L, 4);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C0C0C0C0CL, 2);
+        x = Bits.bitPermuteStep(x, 0x2222222222222222L, 1);
         return x;
     }
 
     public static int shuffle2(int x)
     {
         // "shuffle" (twice) low half to even bits and high half to odd bits
-        int t;
-        t = (x ^ (x >>>  7)) & 0x00AA00AA; x ^= (t ^ (t <<  7));
-        t = (x ^ (x >>> 14)) & 0x0000CCCC; x ^= (t ^ (t << 14));
-        t = (x ^ (x >>>  4)) & 0x00F000F0; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  8)) & 0x0000FF00; x ^= (t ^ (t <<  8));
+        x = Bits.bitPermuteStep(x, 0x00AA00AA, 7);
+        x = Bits.bitPermuteStep(x, 0x0000CCCC, 14);
+        x = Bits.bitPermuteStep(x, 0x00F000F0, 4);
+        x = Bits.bitPermuteStep(x, 0x0000FF00, 8);
+        return x;
+    }
+
+    public static long shuffle2(long x)
+    {
+        // "shuffle" (twice) low half to even bits and high half to odd bits
+        x = Bits.bitPermuteStep(x, 0x00000000FF00FF00L, 24);
+        x = Bits.bitPermuteStep(x, 0x00CC00CC00CC00CCL, 6);
+        x = Bits.bitPermuteStep(x, 0x0000F0F00000F0F0L, 12);
+        x = Bits.bitPermuteStep(x, 0x0A0A0A0A0A0A0A0AL, 3);
+        return x;
+    }
+
+    public static long shuffle3(long x)
+    {
+        // "shuffle" (thrice) low half to even bits and high half to odd bits
+        x = Bits.bitPermuteStep(x, 0x00AA00AA00AA00AAL, 7);
+        x = Bits.bitPermuteStep(x, 0x0000CCCC0000CCCCL, 14);
+        x = Bits.bitPermuteStep(x, 0x00000000F0F0F0F0L, 28);
         return x;
     }
 
     public static int unshuffle(int x)
     {
         // "unshuffle" even bits to low half and odd bits to high half
-        int t;
-        t = (x ^ (x >>>  1)) & 0x22222222; x ^= (t ^ (t <<  1));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  4)) & 0x00F000F0; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  8)) & 0x0000FF00; x ^= (t ^ (t <<  8));
+        x = Bits.bitPermuteStep(x, 0x22222222, 1);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C, 2);
+        x = Bits.bitPermuteStep(x, 0x00F000F0, 4);
+        x = Bits.bitPermuteStep(x, 0x0000FF00, 8);
         return x;
     }
 
     public static long unshuffle(long x)
     {
         // "unshuffle" even bits to low half and odd bits to high half
-        long t;
-        t = (x ^ (x >>>  1)) & 0x2222222222222222L; x ^= (t ^ (t <<  1));
-        t = (x ^ (x >>>  2)) & 0x0C0C0C0C0C0C0C0CL; x ^= (t ^ (t <<  2));
-        t = (x ^ (x >>>  4)) & 0x00F000F000F000F0L; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>>  8)) & 0x0000FF000000FF00L; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>> 16)) & 0x00000000FFFF0000L; x ^= (t ^ (t << 16));
+        x = Bits.bitPermuteStep(x, 0x2222222222222222L, 1);
+        x = Bits.bitPermuteStep(x, 0x0C0C0C0C0C0C0C0CL, 2);
+        x = Bits.bitPermuteStep(x, 0x00F000F000F000F0L, 4);
+        x = Bits.bitPermuteStep(x, 0x0000FF000000FF00L, 8);
+        x = Bits.bitPermuteStep(x, 0x00000000FFFF0000L, 16);
         return x;
     }
 
     public static int unshuffle2(int x)
     {
         // "unshuffle" (twice) even bits to low half and odd bits to high half
-        int t;
-        t = (x ^ (x >>>  8)) & 0x0000FF00; x ^= (t ^ (t <<  8));
-        t = (x ^ (x >>>  4)) & 0x00F000F0; x ^= (t ^ (t <<  4));
-        t = (x ^ (x >>> 14)) & 0x0000CCCC; x ^= (t ^ (t << 14));
-        t = (x ^ (x >>>  7)) & 0x00AA00AA; x ^= (t ^ (t <<  7));
+        x = Bits.bitPermuteStep(x, 0x0000FF00, 8);
+        x = Bits.bitPermuteStep(x, 0x00F000F0, 4);
+        x = Bits.bitPermuteStep(x, 0x0000CCCC, 14);
+        x = Bits.bitPermuteStep(x, 0x00AA00AA, 7);
         return x;
     }
+
+    public static long unshuffle2(long x)
+    {
+        // "unshuffle" (twice) even bits to low half and odd bits to high half
+        x = Bits.bitPermuteStep(x, 0x0A0A0A0A0A0A0A0AL, 3);
+        x = Bits.bitPermuteStep(x, 0x0000F0F00000F0F0L, 12);
+        x = Bits.bitPermuteStep(x, 0x00CC00CC00CC00CCL, 6);
+        x = Bits.bitPermuteStep(x, 0x00000000FF00FF00L, 24);
+        return x;
+    }
+
+    public static long unshuffle3(long x)
+    {
+        // "unshuffle" (thrice) even bits to low half and odd bits to high half
+        return shuffle3(x);
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Mod.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Mod.java
index 4ea43de..fc8f3c8 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Mod.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Mod.java
@@ -2,10 +2,48 @@
 
 import java.util.Random;
 
+import org.bouncycastle.util.Integers;
+
+/*
+ * Modular inversion as implemented in this class is based on the paper "Fast constant-time gcd
+ * computation and modular inversion" by Daniel J. Bernstein and Bo-Yin Yang.
+ */
+
 public abstract class Mod
 {
+    private static final int M30 = 0x3FFFFFFF;
+    private static final long M32L = 0xFFFFFFFFL;
+
+    /** @deprecated Will be removed. */
+    public static void add(int[] p, int[] x, int[] y, int[] z)
+    {
+        int len = p.length;
+        int c = Nat.add(len, x, y, z);
+        if (c != 0)
+        {
+            Nat.subFrom(len, p, z);
+        }
+    }
+
+    public static void checkedModOddInverse(int[] m, int[] x, int[] z)
+    {
+        if (0 == modOddInverse(m, x, z))
+        {
+            throw new ArithmeticException("Inverse does not exist.");
+        }
+    }
+
+    public static void checkedModOddInverseVar(int[] m, int[] x, int[] z)
+    {
+        if (!modOddInverseVar(m, x, z))
+        {
+            throw new ArithmeticException("Inverse does not exist.");
+        }
+    }
+
     public static int inverse32(int d)
     {
+//        assert (d & 1) == 1;
 //        int x = d + (((d + 1) & 4) << 1);   // d.x == 1 mod 2**4
         int x = d;                          // d.x == 1 mod 2**3
         x *= 2 - d * x;                     // d.x == 1 mod 2**6
@@ -16,72 +54,152 @@
         return  x;
     }
 
-    public static void invert(int[] p, int[] x, int[] z)
+    /** @deprecated Use {@link #checkedModOddInverseVar(int[], int[], int[])} instead. */
+    public static void invert(int[] m, int[] x, int[] z)
     {
-        int len = p.length;
-        if (Nat.isZero(len, x))
+        checkedModOddInverseVar(m,  x,  z);
+    }
+
+    public static int modOddInverse(int[] m, int[] x, int[] z)
+    {
+        int len32 = m.length;
+//        assert len32 > 0;
+//        assert (m[0] & 1) != 0;
+//        assert m[len32 - 1] != 0;
+
+        int bits = (len32 << 5) - Integers.numberOfLeadingZeros(m[len32 - 1]);
+        int len30 = (bits + 29) / 30;
+
+        int[] t = new int[4];
+        int[] D = new int[len30];
+        int[] E = new int[len30];
+        int[] F = new int[len30];
+        int[] G = new int[len30];
+        int[] M = new int[len30];
+
+        E[0] = 1;
+        encode30(bits, x, 0, G, 0);
+        encode30(bits, m, 0, M, 0);
+        System.arraycopy(M, 0, F, 0, len30);
+
+        int eta = -1;
+        int m0Inv32 = inverse32(M[0]);
+        int maxDivsteps = getMaximumDivsteps(bits);
+
+        for (int divSteps = 0; divSteps < maxDivsteps; divSteps += 30)
         {
-            throw new IllegalArgumentException("'x' cannot be 0");
-        }
-        if (Nat.isOne(len, x))
-        {
-            System.arraycopy(x, 0, z, 0, len);
-            return;
+            eta = divsteps30(eta, F[0], G[0], t);
+            updateDE30(len30, D, E, t, m0Inv32, M);
+            updateFG30(len30, F, G, t);
         }
 
-        int[] u = Nat.copy(len, x);
-        int[] a = Nat.create(len);
-        a[0] = 1;
-        int ac = 0;
+        int signF = F[len30 - 1] >> 31;
+        cnegate30(len30, signF, F);
 
-        if ((u[0] & 1) == 0)
+        /*
+         * D is in the range (-2.M, M). First, conditionally add M if D is negative, to bring it
+         * into the range (-M, M). Then normalize by conditionally negating (according to signF)
+         * and/or then adding M, to bring it into the range [0, M).
+         */
+        cnormalize30(len30, signF, D, M);
+
+        decode30(bits, D, 0, z, 0);
+//        assert 0 != Nat.lessThan(len32, z, m);
+
+        return Nat.equalTo(len30, F, 1) & Nat.equalToZero(len30, G);
+    }
+
+    public static boolean modOddInverseVar(int[] m, int[] x, int[] z)
+    {
+        int len32 = m.length;
+//        assert len32 > 0;
+//        assert (m[0] & 1) != 0;
+//        assert m[len32 - 1] != 0;
+
+        int bits = (len32 << 5) - Integers.numberOfLeadingZeros(m[len32 - 1]);
+        int len30 = (bits + 29) / 30;
+
+        int[] t = new int[4];
+        int[] D = new int[len30];
+        int[] E = new int[len30];
+        int[] F = new int[len30];
+        int[] G = new int[len30];
+        int[] M = new int[len30];
+
+        E[0] = 1;
+        encode30(bits, x, 0, G, 0);
+        encode30(bits, m, 0, M, 0);
+        System.arraycopy(M, 0, F, 0, len30);
+
+        int clzG = Integers.numberOfLeadingZeros(G[len30 - 1] | 1) - (len30 * 30 + 2 - bits);
+        int eta = -1 - clzG;
+        int lenDE = len30, lenFG = len30;
+        int m0Inv32 = inverse32(M[0]);
+        int maxDivsteps = getMaximumDivsteps(bits);
+
+        int divsteps = 0;
+        while (!Nat.isZero(lenFG, G))
         {
-            ac = inversionStep(p, u, len, a, ac);
-        }
-        if (Nat.isOne(len, u))
-        {
-            inversionResult(p, ac, a, z);
-            return;
-        }
-
-        int[] v = Nat.copy(len, p);
-        int[] b = Nat.create(len);
-        int bc = 0;
-
-        int uvLen = len;
-
-        for (;;)
-        {
-            while (u[uvLen - 1] == 0 && v[uvLen - 1] == 0)
+            if (divsteps >= maxDivsteps)
             {
-                --uvLen;
+                return false;
             }
 
-            if (Nat.gte(uvLen, u, v))
+            divsteps += 30;
+
+            eta = divsteps30Var(eta, F[0], G[0], t);
+            updateDE30(lenDE, D, E, t, m0Inv32, M);
+            updateFG30(lenFG, F, G, t);
+
+            int fn = F[lenFG - 1];
+            int gn = G[lenFG - 1];
+
+            int cond = (lenFG - 2) >> 31;
+            cond |= fn ^ (fn >> 31);
+            cond |= gn ^ (gn >> 31);
+
+            if (cond == 0)
             {
-                Nat.subFrom(uvLen, v, u);
-//              assert (u[0] & 1) == 0;
-                ac += Nat.subFrom(len, b, a) - bc;
-                ac = inversionStep(p, u, uvLen, a, ac);
-                if (Nat.isOne(uvLen, u))
-                {
-                    inversionResult(p, ac, a, z);
-                    return;
-                }
-            }
-            else
-            {
-                Nat.subFrom(uvLen, u, v);
-//              assert (v[0] & 1) == 0;
-                bc += Nat.subFrom(len, a, b) - ac;
-                bc = inversionStep(p, v, uvLen, b, bc);
-                if (Nat.isOne(uvLen, v))
-                {
-                    inversionResult(p, bc, b, z);
-                    return;
-                }
+                F[lenFG - 2] |= fn << 30;
+                G[lenFG - 2] |= gn << 30;
+                --lenFG;
             }
         }
+
+        int signF = F[lenFG - 1] >> 31;
+
+        /*
+         * D is in the range (-2.M, M). First, conditionally add M if D is negative, to bring it
+         * into the range (-M, M). Then normalize by conditionally negating (according to signF)
+         * and/or then adding M, to bring it into the range [0, M).
+         */
+        int signD = D[lenDE - 1] >> 31;
+        if (signD < 0)
+        {
+            signD = add30(lenDE, D, M);
+        }
+        if (signF < 0)
+        {
+            signD = negate30(lenDE, D);
+            signF = negate30(lenFG, F);
+        }
+//        assert 0 == signF;
+
+        if (!Nat.isOne(lenFG, F))
+        {
+            return false;
+        }
+
+        if (signD < 0)
+        {
+            signD = add30(lenDE, D, M);
+        }
+//        assert 0 == signD;
+
+        decode30(bits, D, 0, z, 0);
+//        assert !Nat.gte(len32, z, m);
+
+        return true;
     }
 
     public static int[] random(int[] p)
@@ -110,16 +228,7 @@
         return s;
     }
 
-    public static void add(int[] p, int[] x, int[] y, int[] z)
-    {
-        int len = p.length;
-        int c = Nat.add(len, x, y, z);
-        if (c != 0)
-        {
-            Nat.subFrom(len, p, z);
-        }
-    }
-
+    /** @deprecated Will be removed. */
     public static void subtract(int[] p, int[] x, int[] y, int[] z)
     {
         int len = p.length;
@@ -130,68 +239,347 @@
         }
     }
 
-    private static void inversionResult(int[] p, int ac, int[] a, int[] z)
+    private static int add30(int len30, int[] D, int[] M)
     {
-        if (ac < 0)
+//        assert len30 > 0;
+//        assert D.length >= len30;
+//        assert M.length >= len30;
+
+        int c = 0, last = len30 - 1;
+        for (int i = 0; i < last; ++i)
         {
-            Nat.add(p.length, a, p, z);
+            c += D[i] + M[i];
+            D[i] = c & M30; c >>= 30;
         }
-        else
-        {
-            System.arraycopy(a, 0, z, 0, p.length);
-        }
+        c += D[last] + M[last];
+        D[last] = c; c >>= 30;
+        return c;
     }
 
-    private static int inversionStep(int[] p, int[] u, int uLen, int[] x, int xc)
+    private static void cnegate30(int len30, int cond, int[] D)
     {
-        int len = p.length;
-        int count = 0;
-        while (u[0] == 0)
+//        assert len30 > 0;
+//        assert D.length >= len30;
+
+        int c = 0, last = len30 - 1;
+        for (int i = 0; i < last; ++i)
         {
-            Nat.shiftDownWord(uLen, u, 0);
-            count += 32;
+            c += (D[i] ^ cond) - cond;
+            D[i] = c & M30; c >>= 30;
         }
+        c += (D[last] ^ cond) - cond;
+        D[last] = c;
+    }
+
+    private static void cnormalize30(int len30, int condNegate, int[] D, int[] M)
+    {
+//        assert len30 > 0;
+//        assert D.length >= len30;
+//        assert M.length >= len30;
+
+        int last = len30 - 1;
 
         {
-            int zeroes = getTrailingZeroes(u[0]);
-            if (zeroes > 0)
+            int c = 0, condAdd = D[last] >> 31;
+            for (int i = 0; i < last; ++i)
             {
-                Nat.shiftDownBits(uLen, u, zeroes, 0);
-                count += zeroes;
+                int di = D[i] + (M[i] & condAdd);
+                di = (di ^ condNegate) - condNegate;
+                c += di; D[i] = c & M30; c >>= 30;
+            }
+            {
+                int di = D[last] + (M[last] & condAdd);
+                di = (di ^ condNegate) - condNegate;
+                c += di; D[last] = c;
             }
         }
 
-        for (int i = 0; i < count; ++i)
         {
-            if ((x[0] & 1) != 0)
+            int c = 0, condAdd = D[last] >> 31;
+            for (int i = 0; i < last; ++i)
             {
-                if (xc < 0)
-                {
-                    xc += Nat.addTo(len, p, x);
-                }
-                else
-                {
-                    xc += Nat.subFrom(len, p, x);
-                }
+                int di = D[i] + (M[i] & condAdd);
+                c += di; D[i] = c & M30; c >>= 30;
             }
-
-//            assert xc == 0 || xc == 1;
-            Nat.shiftDownBit(len, x, xc);
+            {
+                int di = D[last] + (M[last] & condAdd);
+                c += di; D[last] = c;
+            }
+//            assert c >> 30 == 0;
         }
-        
-        return xc;
     }
 
-    private static int getTrailingZeroes(int x)
+    private static void decode30(int bits, int[] x, int xOff, int[] z, int zOff)
     {
-//        assert x != 0;
+//        assert bits > 0;
+//        assert x != z;
 
-        int count = 0;
-        while ((x & 1) == 0)
+        int avail = 0;
+        long data = 0L;
+
+        while (bits > 0)
         {
-            x >>>= 1;
-            ++count;
+            while (avail < Math.min(32, bits))
+            {
+                data |= (long)x[xOff++] << avail;
+                avail += 30;
+            }
+
+            z[zOff++] = (int)data; data >>>= 32;
+            avail -= 32;
+            bits -= 32;
         }
-        return count;
+    }
+
+    private static int divsteps30(int eta, int f0, int g0, int[] t)
+    {
+        int u = 1, v = 0, q = 0, r = 1;
+        int f = f0, g = g0;
+
+        for (int i = 0; i < 30; ++i)
+        {
+//            assert (f & 1) == 1;
+//            assert (u * f0 + v * g0) == f << i;
+//            assert (q * f0 + r * g0) == g << i;
+
+            int c1 = eta >> 31;
+            int c2 = -(g & 1);
+
+            int x = (f ^ c1) - c1;
+            int y = (u ^ c1) - c1;
+            int z = (v ^ c1) - c1;
+
+            g += x & c2;
+            q += y & c2;
+            r += z & c2;
+
+            c1 &= c2;
+            eta = (eta ^ c1) - (c1 + 1);
+
+            f += g & c1;
+            u += q & c1;
+            v += r & c1;
+
+            g >>= 1;
+            u <<= 1;
+            v <<= 1;
+        }
+
+        t[0] = u;
+        t[1] = v;
+        t[2] = q;
+        t[3] = r;
+
+        return eta;
+    }
+
+    private static int divsteps30Var(int eta, int f0, int g0, int[] t)
+    {
+        int u = 1, v = 0, q = 0, r = 1;
+        int f = f0, g = g0, m, w, x, y, z;
+        int i = 30, limit, zeros;
+
+        for (;;)
+        {
+            // Use a sentinel bit to count zeros only up to i.
+            zeros = Integers.numberOfTrailingZeros(g | (-1 << i));
+
+            g >>= zeros;
+            u <<= zeros;
+            v <<= zeros;
+            eta -= zeros;
+            i -= zeros;
+
+            if (i <= 0)
+            {
+                break;
+            }
+
+//            assert (f & 1) == 1;
+//            assert (g & 1) == 1;
+//            assert (u * f0 + v * g0) == f << (30 - i);
+//            assert (q * f0 + r * g0) == g << (30 - i);
+
+            if (eta < 0)
+            {
+                eta = -eta;
+                x = f; f = g; g = -x;
+                y = u; u = q; q = -y;
+                z = v; v = r; r = -z;
+
+                // Handle up to 6 divsteps at once, subject to eta and i.
+                limit = (eta + 1) > i ? i : (eta + 1);
+                m = (-1 >>> (32 - limit)) & 63;
+
+                w = (f * g * (f * f - 2)) & m;
+            }
+            else
+            {
+                // Handle up to 4 divsteps at once, subject to eta and i.
+                limit = (eta + 1) > i ? i : (eta + 1);
+                m = (-1 >>> (32 - limit)) & 15;
+
+                w = f + (((f + 1) & 4) << 1);
+                w = (-w * g) & m;
+            }
+
+            g += f * w;
+            q += u * w;
+            r += v * w;
+
+//            assert (g & m) == 0;
+        }
+
+        t[0] = u;
+        t[1] = v;
+        t[2] = q;
+        t[3] = r;
+
+        return eta;
+    }
+
+    private static void encode30(int bits, int[] x, int xOff, int[] z, int zOff)
+    {
+//        assert bits > 0;
+//        assert x != z;
+
+        int avail = 0;
+        long data = 0L;
+
+        while (bits > 0)
+        {
+            if (avail < Math.min(30, bits))
+            {
+                data |= (x[xOff++] & M32L) << avail;
+                avail += 32;
+            }
+
+            z[zOff++] = (int)data & M30; data >>>= 30;
+            avail -= 30;
+            bits -= 30;
+        }
+    }
+
+    private static int getMaximumDivsteps(int bits)
+    {
+        return (49 * bits + (bits < 46 ? 80 : 47)) / 17;
+    }
+
+    private static int negate30(int len30, int[] D)
+    {
+//        assert len30 > 0;
+//        assert D.length >= len30;
+
+        int c = 0, last = len30 - 1;
+        for (int i = 0; i < last; ++i)
+        {
+            c -= D[i];
+            D[i] = c & M30; c >>= 30;
+        }
+        c -= D[last];
+        D[last] = c; c >>= 30;
+        return c;
+    }
+
+    private static void updateDE30(int len30, int[] D, int[] E, int[] t, int m0Inv32, int[] M)
+    {
+//        assert len30 > 0;
+//        assert D.length >= len30;
+//        assert E.length >= len30;
+//        assert M.length >= len30;
+//        assert m0Inv32 * M[0] == 1;
+
+        final int u = t[0], v = t[1], q = t[2], r = t[3];
+        int di, ei, i, md, me, mi, sd, se;
+        long cd, ce;
+
+        /*
+         * We accept D (E) in the range (-2.M, M) and conceptually add the modulus to the input
+         * value if it is initially negative. Instead of adding it explicitly, we add u and/or v (q
+         * and/or r) to md (me).
+         */
+        sd = D[len30 - 1] >> 31;
+        se = E[len30 - 1] >> 31;
+
+        md = (u & sd) + (v & se);
+        me = (q & sd) + (r & se);
+
+        mi = M[0];
+        di = D[0];
+        ei = E[0];
+
+        cd = (long)u * di + (long)v * ei;
+        ce = (long)q * di + (long)r * ei;
+
+        /*
+         * Subtract from md/me an extra term in the range [0, 2^30) such that the low 30 bits of the
+         * intermediate D/E values will be 0, allowing clean division by 2^30. The final D/E are
+         * thus in the range (-2.M, M), consistent with the input constraint.
+         */
+        md -= (m0Inv32 * (int)cd + md) & M30;
+        me -= (m0Inv32 * (int)ce + me) & M30;
+
+        cd += (long)mi * md;
+        ce += (long)mi * me;
+
+//        assert ((int)cd & M30) == 0;
+//        assert ((int)ce & M30) == 0;
+
+        cd >>= 30;
+        ce >>= 30;
+
+        for (i = 1; i < len30; ++i)
+        {
+            mi = M[i];
+            di = D[i];
+            ei = E[i];
+
+            cd += (long)u * di + (long)v * ei + (long)mi * md;
+            ce += (long)q * di + (long)r * ei + (long)mi * me;
+
+            D[i - 1] = (int)cd & M30; cd >>= 30;
+            E[i - 1] = (int)ce & M30; ce >>= 30;
+        }
+
+        D[len30 - 1] = (int)cd;
+        E[len30 - 1] = (int)ce;
+    }
+
+    private static void updateFG30(int len30, int[] F, int[] G, int[] t)
+    {
+//        assert len30 > 0;
+//        assert F.length >= len30;
+//        assert G.length >= len30;
+
+        final int u = t[0], v = t[1], q = t[2], r = t[3];
+        int fi, gi, i;
+        long cf, cg;
+
+        fi = F[0];
+        gi = G[0];
+
+        cf = (long)u * fi + (long)v * gi;
+        cg = (long)q * fi + (long)r * gi;
+
+//        assert ((int)cf & M30) == 0;
+//        assert ((int)cg & M30) == 0;
+
+        cf >>= 30;
+        cg >>= 30;
+
+        for (i = 1; i < len30; ++i)
+        {
+            fi = F[i];
+            gi = G[i];
+
+            cf += (long)u * fi + (long)v * gi;
+            cg += (long)q * fi + (long)r * gi;
+
+            F[i - 1] = (int)cf & M30; cf >>= 30;
+            G[i - 1] = (int)cg & M30; cg >>= 30;
+        }
+
+        F[len30 - 1] = (int)cf;
+        G[len30 - 1] = (int)cg;
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat.java
index 68bdb66..28745df 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat.java
@@ -254,6 +254,34 @@
 //        }
     }
 
+    public static int compare(int len, int[] x, int[] y)
+    {
+        for (int i = len - 1; i >= 0; --i)
+        {
+            int x_i = x[i] ^ Integer.MIN_VALUE;
+            int y_i = y[i] ^ Integer.MIN_VALUE;
+            if (x_i < y_i)
+                return -1;
+            if (x_i > y_i)
+                return 1;
+        }
+        return 0;
+    }
+
+    public static int compare(int len, int[] x, int xOff, int[] y, int yOff)
+    {
+        for (int i = len - 1; i >= 0; --i)
+        {
+            int x_i = x[xOff + i] ^ Integer.MIN_VALUE;
+            int y_i = y[yOff + i] ^ Integer.MIN_VALUE;
+            if (x_i < y_i)
+                return -1;
+            if (x_i > y_i)
+                return 1;
+        }
+        return 0;
+    }
+
     public static int[] copy(int len, int[] x)
     {
         int[] z = new int[len];
@@ -409,6 +437,72 @@
         return true;
     }
 
+    public static int equalTo(int len, int[] x, int y)
+    {
+        int d = x[0] ^ y;
+        for (int i = 1; i < len; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
+    public static int equalTo(int len, int[] x, int xOff, int y)
+    {
+        int d = x[xOff] ^ y;
+        for (int i = 1; i < len; ++i)
+        {
+            d |= x[xOff + i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
+    public static int equalTo(int len, int[] x, int[] y)
+    {
+        int d = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            d |= x[i] ^ y[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
+    public static int equalTo(int len, int[] x, int xOff, int[] y, int yOff)
+    {
+        int d = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            d |= x[xOff + i] ^ y[yOff + i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
+    public static int equalToZero(int len, int[] x)
+    {
+        int d = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            d |= x[i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
+    public static int equalToZero(int len, int[] x, int xOff)
+    {
+        int d = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            d |= x[xOff + i];
+        }
+        d = (d >>> 1) | (d & 1);
+        return (d - 1) >> 31;
+    }
+
     public static int[] fromBigInteger(int bits, BigInteger x)
     {
         if (x.signum() < 0 || x.bitLength() > bits)
@@ -418,10 +512,11 @@
 
         int len = (bits + 31) >> 5;
         int[] z = create(len);
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < len; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
@@ -436,10 +531,11 @@
 
         int len = (bits + 63) >> 6;
         long[] z = create64(len);
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < len; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
@@ -575,6 +671,30 @@
         return true;
     }
 
+    public static int lessThan(int len, int[] x, int[] y)
+    {
+        long c = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            c += (x[i] & M) - (y[i] & M);
+            c >>= 32;
+        }
+//        assert c == 0L || c == -1L;
+        return (int)c;
+    }
+
+    public static int lessThan(int len, int[] x, int xOff, int[] y, int yOff)
+    {
+        long c = 0;
+        for (int i = 0; i < len; ++i)
+        {
+            c += (x[xOff + i] & M) - (y[yOff + i] & M);
+            c >>= 32;
+        }
+//        assert c == 0L || c == -1L;
+        return (int)c;
+    }
+
     public static void mul(int len, int[] x, int[] y, int[] zz)
     {
         zz[len] = mulWord(len, x[0], y, zz);
@@ -1023,7 +1143,7 @@
     }
 
     /**
-     * @deprecated Use {@link #squareWordAddTo(int[], int, int, int[], int) instead.
+     * @deprecated Use {@link #squareWordAddTo(int[], int, int, int[], int)} instead.
      */
     public static int squareWordAdd(int[] x, int xOff, int xPos, int[] z, int zOff)
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat128.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat128.java
index 828a00e..0ea22eb 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat128.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat128.java
@@ -196,10 +196,11 @@
         }
 
         int[] z = create();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 4; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
@@ -213,10 +214,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 2; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat160.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat160.java
index e33cecc..b35c521 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat160.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat160.java
@@ -180,10 +180,11 @@
         }
 
         int[] z = create();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 5; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat192.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat192.java
index f29a8f2..9a84e6a 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat192.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat192.java
@@ -234,10 +234,11 @@
         }
 
         int[] z = create();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 6; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
@@ -251,10 +252,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 3; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat224.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat224.java
index 5d14f27..e4b7d5e 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat224.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat224.java
@@ -270,10 +270,11 @@
         }
 
         int[] z = create();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 7; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat256.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat256.java
index 8423b8c..20b17cd 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat256.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat256.java
@@ -332,10 +332,11 @@
         }
 
         int[] z = create();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 8; ++i)
         {
-            z[i++] = x.intValue();
+            z[i] = x.intValue();
             x = x.shiftRight(32);
         }
         return z;
@@ -349,10 +350,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 4; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat320.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat320.java
index 2ee3f64..81968af 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat320.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat320.java
@@ -54,10 +54,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 5; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat448.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat448.java
index 3d93973..40f652c 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat448.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat448.java
@@ -58,10 +58,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 7; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat576.java b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat576.java
index 82e28e2..19281c3 100644
--- a/bcprov/src/main/java/org/bouncycastle/math/raw/Nat576.java
+++ b/bcprov/src/main/java/org/bouncycastle/math/raw/Nat576.java
@@ -62,10 +62,11 @@
         }
 
         long[] z = create64();
-        int i = 0;
-        while (x.signum() != 0)
+
+        // NOTE: Use a fixed number of loop iterations
+        for (int i = 0; i < 9; ++i)
         {
-            z[i++] = x.longValue();
+            z[i] = x.longValue();
             x = x.shiftRight(64);
         }
         return z;
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ExhaustedPrivateKeyException.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ExhaustedPrivateKeyException.java
new file mode 100644
index 0000000..7fe4cc1
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ExhaustedPrivateKeyException.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.pqc.crypto;
+
+public class ExhaustedPrivateKeyException
+    extends IllegalStateException
+{
+    public ExhaustedPrivateKeyException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java
index 95fff30..1437d43 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/GMSSKeyPairGenerator.java
@@ -5,7 +5,6 @@
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
-import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.Digest;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.pqc.crypto.gmss.util.GMSSRandom;
@@ -429,8 +428,8 @@
         this.currentSeeds = new byte[numLayer][mdLength];
         this.nextNextSeeds = new byte[numLayer - 1][mdLength];
 
-        // construct SecureRandom for initial seed generation
-        SecureRandom secRan = CryptoServicesRegistrar.getSecureRandom();
+        // SecureRandom for initial seed generation
+        SecureRandom secRan = param.getRandom();
 
         // generation of initial seeds
         for (int i = 0; i < numLayer; i++)
@@ -452,7 +451,7 @@
         int[] defw = {3, 3, 3, 3};
         int[] defk = {2, 2, 2, 2};
 
-        KeyGenerationParameters kgp = new GMSSKeyGenerationParameters(CryptoServicesRegistrar.getSecureRandom(), new GMSSParameters(defh.length, defh, defw, defk));
+        KeyGenerationParameters kgp = new GMSSKeyGenerationParameters(null, new GMSSParameters(defh.length, defh, defw, defk));
         this.initialize(kgp);
 
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/Treehash.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/Treehash.java
index 797355c..e92f386 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/Treehash.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/Treehash.java
@@ -181,8 +181,7 @@
     {
         if (!this.seedInitialized)
         {
-            System.err.println("Seed " + this.maxHeight + " not initialized");
-            return;
+            throw new IllegalStateException("Seed " + this.maxHeight + " not initialized");
         }
 
         this.heightOfNodes = new Vector();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java
index d012ce7..4c2e501 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSVerify.java
@@ -10,9 +10,10 @@
  */
 public class WinternitzOTSVerify
 {
-
     private Digest messDigestOTS;
 
+    private int mdsize;
+
     /**
      * The Winternitz parameter
      */
@@ -30,6 +31,7 @@
         this.w = w;
 
         messDigestOTS = digest;
+        mdsize = messDigestOTS.getDigestSize();
     }
 
     /**
@@ -56,13 +58,10 @@
      */
     public byte[] Verify(byte[] message, byte[] signature)
     {
-
-        int mdsize = messDigestOTS.getDigestSize();
         byte[] hash = new byte[mdsize]; // hash of message m
 
         // create hash of message m
         messDigestOTS.update(message, 0, message.length);
-        hash = new byte[messDigestOTS.getDigestSize()];
         messDigestOTS.doFinal(hash, 0);
 
         int size = ((mdsize << 3) + (w - 1)) / w;
@@ -86,7 +85,6 @@
         {
             int d = 8 / w;
             int k = (1 << w) - 1;
-            byte[] hlp = new byte[mdsize];
 
             // verify signature
             for (int i = 0; i < hash.length; i++)
@@ -95,18 +93,7 @@
                 {
                     test = hash[i] & k;
                     c += test;
-
-                    System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
-
-                    while (test < k)
-                    {
-                        messDigestOTS.update(hlp, 0, hlp.length);
-                        hlp = new byte[messDigestOTS.getDigestSize()];
-                        messDigestOTS.doFinal(hlp, 0);
-                        test++;
-                    }
-
-                    System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                    hashSignatureBlock(signature, counter * mdsize, k - test, testKey, counter * mdsize);
                     hash[i] = (byte)(hash[i] >>> w);
                     counter++;
                 }
@@ -116,17 +103,7 @@
             for (int i = 0; i < logs; i += w)
             {
                 test = c & k;
-
-                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
-
-                while (test < k)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test++;
-                }
-                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                hashSignatureBlock(signature, counter * mdsize, k - test, testKey, counter * mdsize);
                 c >>>= w;
                 counter++;
             }
@@ -135,7 +112,6 @@
         {
             int d = mdsize / w;
             int k = (1 << w) - 1;
-            byte[] hlp = new byte[mdsize];
             long big8;
             int ii = 0;
             // create signature
@@ -152,18 +128,7 @@
                 {
                     test = (int)(big8 & k);
                     c += test;
-
-                    System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
-
-                    while (test < k)
-                    {
-                        messDigestOTS.update(hlp, 0, hlp.length);
-                        hlp = new byte[messDigestOTS.getDigestSize()];
-                        messDigestOTS.doFinal(hlp, 0);
-                        test++;
-                    }
-
-                    System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                    hashSignatureBlock(signature, counter * mdsize, k - test, testKey, counter * mdsize);
                     big8 >>>= w;
                     counter++;
                 }
@@ -181,18 +146,7 @@
             {
                 test = (int)(big8 & k);
                 c += test;
-
-                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
-
-                while (test < k)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test++;
-                }
-
-                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                hashSignatureBlock(signature, counter * mdsize, k - test, testKey, counter * mdsize);
                 big8 >>>= w;
                 counter++;
             }
@@ -202,18 +156,7 @@
             for (int i = 0; i < logs; i += w)
             {
                 test = c & k;
-
-                System.arraycopy(signature, counter * mdsize, hlp, 0, mdsize);
-
-                while (test < k)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test++;
-                }
-
-                System.arraycopy(hlp, 0, testKey, counter * mdsize, mdsize);
+                hashSignatureBlock(signature, counter * mdsize, k - test, testKey, counter * mdsize);
                 c >>>= w;
                 counter++;
             }
@@ -251,7 +194,6 @@
                 while (test8 < k)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8++;
                 }
@@ -282,7 +224,6 @@
                 while (test8 < k)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8++;
                 }
@@ -301,7 +242,6 @@
                 while (test8 < k)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8++;
                 }
@@ -312,13 +252,11 @@
             }
         }// end if(w<57)
 
-        byte[] TKey = new byte[mdsize];
         messDigestOTS.update(testKey, 0, testKey.length);
-        TKey = new byte[messDigestOTS.getDigestSize()];
+
+        byte[] TKey = new byte[mdsize];
         messDigestOTS.doFinal(TKey, 0);
-
         return TKey;
-
     }
 
     /**
@@ -341,4 +279,22 @@
         return log;
     }
 
+    private void hashSignatureBlock(byte[] sig, int sigOff, int rounds, byte[] buf, int bufOff)
+    {
+        if (rounds < 1)
+        {
+            System.arraycopy(sig, sigOff, buf, bufOff, mdsize);
+        }
+        else
+        {
+            messDigestOTS.update(sig, sigOff, mdsize);
+            messDigestOTS.doFinal(buf, bufOff);
+
+            while (--rounds > 0)
+            {
+                messDigestOTS.update(buf, bufOff, mdsize);
+                messDigestOTS.doFinal(buf, bufOff);
+            }
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java
index 23bf3fa..931ed48 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/gmss/util/WinternitzOTSignature.java
@@ -59,7 +59,6 @@
      */
     public WinternitzOTSignature(byte[] seed0, Digest digest, int w)
     {
-        // this.name = name;
         this.w = w;
 
         messDigestOTS = digest;
@@ -70,24 +69,13 @@
         // array
 
         mdsize = messDigestOTS.getDigestSize();
-        int mdsizeBit = mdsize << 3;
-        messagesize = (int)Math.ceil((double)(mdsizeBit) / (double)w);
+        messagesize = ((mdsize << 3) + w - 1) / w;
 
         checksumsize = getLog((messagesize << w) + 1);
+        keysize = messagesize + (checksumsize + w - 1) / w;
 
-        keysize = messagesize
-            + (int)Math.ceil((double)checksumsize / (double)w);
-
-        /*
-           * mdsize = messDigestOTS.getDigestLength(); messagesize =
-           * ((mdsize<<3)+(w-1))/w;
-           *
-           * checksumsize = getlog((messagesize<<w)+1);
-           *
-           * keysize = messagesize + (checksumsize+w-1)/w;
-           */
         // define the private key messagesize
-        privateKeyOTS = new byte[keysize][mdsize];
+        privateKeyOTS = new byte[keysize][];
 
         // gmssRandom.setSeed(seed0);
         byte[] dummy = new byte[mdsize];
@@ -114,28 +102,21 @@
      */
     public byte[] getPublicKey()
     {
-        byte[] helppubKey = new byte[keysize * mdsize];
+        byte[] buf = new byte[keysize * mdsize];
 
-        byte[] help = new byte[mdsize];
-        int two_power_t = 1 << w;
+        int pos = 0;
+        int rounds = (1 << w) - 1;
 
         for (int i = 0; i < keysize; i++)
         {
             // hash w-1 time the private key and assign it to the public key
-            messDigestOTS.update(privateKeyOTS[i], 0, privateKeyOTS[i].length);
-            help = new byte[messDigestOTS.getDigestSize()];
-            messDigestOTS.doFinal(help, 0);
-            for (int j = 2; j < two_power_t; j++)
-            {
-                messDigestOTS.update(help, 0, help.length);
-                help = new byte[messDigestOTS.getDigestSize()];
-                messDigestOTS.doFinal(help, 0);
-            }
-            System.arraycopy(help, 0, helppubKey, mdsize * i, mdsize);
+            hashPrivateKeyBlock(i, rounds, buf, pos);
+            pos += mdsize;
         }
 
-        messDigestOTS.update(helppubKey, 0, helppubKey.length);
-        byte[] tmp = new byte[messDigestOTS.getDigestSize()];
+        messDigestOTS.update(buf, 0, buf.length);
+
+        byte[] tmp = new byte[mdsize];
         messDigestOTS.doFinal(tmp, 0);
         return tmp;
     }
@@ -154,14 +135,12 @@
         int test = 0;
         // create hash of message m
         messDigestOTS.update(message, 0, message.length);
-        hash = new byte[messDigestOTS.getDigestSize()];
         messDigestOTS.doFinal(hash, 0);
 
         if (8 % w == 0)
         {
             int d = 8 / w;
             int k = (1 << w) - 1;
-            byte[] hlp = new byte[mdsize];
 
             // create signature
             for (int i = 0; i < hash.length; i++)
@@ -170,17 +149,7 @@
                 {
                     test = hash[i] & k;
                     c += test;
-
-                    System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
-
-                    while (test > 0)
-                    {
-                        messDigestOTS.update(hlp, 0, hlp.length);
-                        hlp = new byte[messDigestOTS.getDigestSize()];
-                        messDigestOTS.doFinal(hlp, 0);
-                        test--;
-                    }
-                    System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                    hashPrivateKeyBlock(counter, test, sign, counter * mdsize);
                     hash[i] = (byte)(hash[i] >>> w);
                     counter++;
                 }
@@ -190,17 +159,7 @@
             for (int i = 0; i < checksumsize; i += w)
             {
                 test = c & k;
-
-                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
-
-                while (test > 0)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test--;
-                }
-                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                hashPrivateKeyBlock(counter, test, sign, counter * mdsize);
                 c >>>= w;
                 counter++;
             }
@@ -209,7 +168,6 @@
         {
             int d = mdsize / w;
             int k = (1 << w) - 1;
-            byte[] hlp = new byte[mdsize];
             long big8;
             int ii = 0;
             // create signature
@@ -224,19 +182,9 @@
                 }
                 for (int j = 0; j < 8; j++)
                 {
-                    test = (int)(big8 & k);
+                    test = (int)big8 & k;
                     c += test;
-
-                    System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
-
-                    while (test > 0)
-                    {
-                        messDigestOTS.update(hlp, 0, hlp.length);
-                        hlp = new byte[messDigestOTS.getDigestSize()];
-                        messDigestOTS.doFinal(hlp, 0);
-                        test--;
-                    }
-                    System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                    hashPrivateKeyBlock(counter, test, sign, counter * mdsize);
                     big8 >>>= w;
                     counter++;
                 }
@@ -252,19 +200,9 @@
             d <<= 3;
             for (int j = 0; j < d; j += w)
             {
-                test = (int)(big8 & k);
+                test = (int)big8 & k;
                 c += test;
-
-                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
-
-                while (test > 0)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test--;
-                }
-                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                hashPrivateKeyBlock(counter, test, sign, counter * mdsize);
                 big8 >>>= w;
                 counter++;
             }
@@ -274,17 +212,7 @@
             for (int i = 0; i < checksumsize; i += w)
             {
                 test = c & k;
-
-                System.arraycopy(privateKeyOTS[counter], 0, hlp, 0, mdsize);
-
-                while (test > 0)
-                {
-                    messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
-                    messDigestOTS.doFinal(hlp, 0);
-                    test--;
-                }
-                System.arraycopy(hlp, 0, sign, counter * mdsize, mdsize);
+                hashPrivateKeyBlock(counter, test, sign, counter * mdsize);
                 c >>>= w;
                 counter++;
             }
@@ -321,7 +249,6 @@
                 while (test8 > 0)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8--;
                 }
@@ -350,7 +277,6 @@
                 while (test8 > 0)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8--;
                 }
@@ -368,7 +294,6 @@
                 while (test8 > 0)
                 {
                     messDigestOTS.update(hlp, 0, hlp.length);
-                    hlp = new byte[messDigestOTS.getDigestSize()];
                     messDigestOTS.doFinal(hlp, 0);
                     test8--;
                 }
@@ -401,4 +326,22 @@
         return log;
     }
 
+    private void hashPrivateKeyBlock(int index, int rounds, byte[] buf, int off)
+    {
+        if (rounds < 1)
+        {
+            System.arraycopy(privateKeyOTS[index], 0, buf, off, mdsize);
+        }
+        else
+        {
+            messDigestOTS.update(privateKeyOTS[index], 0, mdsize);
+            messDigestOTS.doFinal(buf, off);
+
+            while (--rounds > 0)
+            {
+                messDigestOTS.update(buf, off, mdsize);
+                messDigestOTS.doFinal(buf, off);
+            }
+        }
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/Composer.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/Composer.java
new file mode 100644
index 0000000..40f87bf
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/Composer.java
@@ -0,0 +1,186 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayOutputStream;
+
+import org.bouncycastle.util.Encodable;
+
+/**
+ * Type to assist in build LMS messages.
+ */
+public class Composer
+{
+    private final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+    private Composer()
+    {
+
+    }
+
+    public static Composer compose()
+    {
+        return new Composer();
+    }
+
+    public Composer u64str(long n)
+    {
+        u32str((int)(n >>> 32));
+        u32str((int)n);
+
+        return this;
+    }
+
+    public Composer u32str(int n)
+    {
+        bos.write((byte)(n >>> 24));
+        bos.write((byte)(n >>> 16));
+        bos.write((byte)(n >>> 8));
+        bos.write((byte)(n));
+        return this;
+    }
+
+    public Composer u16str(int n)
+    {
+        n &= 0xFFFF;
+        bos.write((byte)(n >>> 8));
+        bos.write((byte)(n));
+        return this;
+    }
+
+    public Composer bytes(Encodable[] encodable)
+    {
+        try
+        {
+            for (Encodable e : encodable)
+            {
+                bos.write(e.getEncoded());
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+
+    public Composer bytes(Encodable encodable)
+    {
+        try
+        {
+            bos.write(encodable.getEncoded());
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+    public Composer pad(int v, int len)
+    {
+        for (; len >= 0; len--)
+        {
+            try
+            {
+
+                bos.write(v);
+
+            }
+            catch (Exception ex)
+            {
+                throw new RuntimeException(ex.getMessage(), ex);
+            }
+        }
+
+        return this;
+    }
+
+    public Composer bytes(byte[][] arrays)
+    {
+        try
+        {
+            for (byte[] array : arrays)
+            {
+                bos.write(array);
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+    public Composer bytes(byte[][] arrays, int start, int end)
+    {
+        try
+        {
+            int j = start;
+            while (j != end)
+            {
+                bos.write(arrays[j]);
+                j++;
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+
+    public Composer bytes(byte[] array)
+    {
+        try
+        {
+            bos.write(array);
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+
+    public Composer bytes(byte[] array, int start, int len)
+    {
+        try
+        {
+            bos.write(array, start, len);
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+
+        return this;
+    }
+
+    public byte[] build()
+    {
+        return bos.toByteArray();
+    }
+
+    public Composer padUntil(int v, int requiredLen)
+    {
+        while (bos.size() < requiredLen)
+        {
+            bos.write(v);
+        }
+
+        return this;
+    }
+
+    public Composer bool(boolean v)
+    {
+        bos.write(v ? 1 : 0);
+        return this;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/DigestUtil.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/DigestUtil.java
new file mode 100644
index 0000000..43e3b16
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/DigestUtil.java
@@ -0,0 +1,88 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Xof;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.digests.SHA512Digest;
+import org.bouncycastle.crypto.digests.SHAKEDigest;
+
+/**
+ * LMS digest utils provides oid mapping to provider digest name.
+ */
+class DigestUtil
+{
+    private static Map<String, ASN1ObjectIdentifier> nameToOid = new HashMap<String, ASN1ObjectIdentifier>();
+    private static Map<ASN1ObjectIdentifier, String> oidToName = new HashMap<ASN1ObjectIdentifier, String>();
+
+    static
+    {
+        nameToOid.put("SHA-256", NISTObjectIdentifiers.id_sha256);
+        nameToOid.put("SHA-512", NISTObjectIdentifiers.id_sha512);
+        nameToOid.put("SHAKE128", NISTObjectIdentifiers.id_shake128);
+        nameToOid.put("SHAKE256", NISTObjectIdentifiers.id_shake256);
+
+        oidToName.put(NISTObjectIdentifiers.id_sha256, "SHA-256");
+        oidToName.put(NISTObjectIdentifiers.id_sha512, "SHA-512");
+        oidToName.put(NISTObjectIdentifiers.id_shake128, "SHAKE128");
+        oidToName.put(NISTObjectIdentifiers.id_shake256, "SHAKE256");
+    }
+
+    static Digest getDigest(ASN1ObjectIdentifier oid)
+    {
+        if (oid.equals(NISTObjectIdentifiers.id_sha256))
+        {
+            return new SHA256Digest();
+        }
+        if (oid.equals(NISTObjectIdentifiers.id_sha512))
+        {
+            return new SHA512Digest();
+        }
+        if (oid.equals(NISTObjectIdentifiers.id_shake128))
+        {
+            return new SHAKEDigest(128);
+        }
+        if (oid.equals(NISTObjectIdentifiers.id_shake256))
+        {
+            return new SHAKEDigest(256);
+        }
+
+        throw new IllegalArgumentException("unrecognized digest OID: " + oid);
+    }
+
+    static String getDigestName(ASN1ObjectIdentifier oid)
+    {
+        String name = oidToName.get(oid);
+        if (name != null)
+        {
+            return name;
+        }
+
+        throw new IllegalArgumentException("unrecognized digest oid: " + oid);
+    }
+
+    static ASN1ObjectIdentifier getDigestOID(String name)
+    {
+        ASN1ObjectIdentifier oid = nameToOid.get(name);
+        if (oid != null)
+        {
+            return oid;
+        }
+
+        throw new IllegalArgumentException("unrecognized digest name: " + name);
+    }
+
+    public static int getDigestSize(Digest digest)
+    {
+        if (digest instanceof Xof)
+        {
+            return digest.getDigestSize() * 2;
+        }
+
+        return digest.getDigestSize();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSS.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSS.java
new file mode 100644
index 0000000..aed2d22
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSS.java
@@ -0,0 +1,237 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.util.List;
+
+import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
+
+class HSS
+{
+
+    public static HSSPrivateKeyParameters generateHSSKeyPair(HSSKeyGenerationParameters parameters)
+    {
+        //
+        // LmsPrivateKey can derive and hold the public key so we just use an array of those.
+        //
+        LMSPrivateKeyParameters[] keys = new LMSPrivateKeyParameters[parameters.getDepth()];
+        LMSSignature[] sig = new LMSSignature[parameters.getDepth() - 1];
+
+        byte[] rootSeed = new byte[32];
+        parameters.getRandom().nextBytes(rootSeed);
+
+        byte[] I = new byte[16];
+        parameters.getRandom().nextBytes(I);
+
+        //
+        // Set the HSS key up with a valid root LMSPrivateKeyParameters and placeholders for the remaining LMS keys.
+        // The placeholders pass enough information to allow the HSSPrivateKeyParameters to be properly reset to an
+        // index of zero. Rather than repeat the same reset-to-index logic in this static method.
+        //
+
+        byte[] zero = new byte[0];
+
+        long hssKeyMaxIndex = 1;
+        for (int t = 0; t < keys.length; t++)
+        {
+            if (t == 0)
+            {
+                keys[t] = new LMSPrivateKeyParameters(
+                    parameters.getLmsParameters()[t].getLMSigParam(),
+                    parameters.getLmsParameters()[t].getLMOTSParam(),
+                    0,
+                    I,
+                    1 << parameters.getLmsParameters()[t].getLMSigParam().getH(),
+                    rootSeed);
+            }
+            else
+            {
+                keys[t] = new PlaceholderLMSPrivateKey(
+                    parameters.getLmsParameters()[t].getLMSigParam(),
+                    parameters.getLmsParameters()[t].getLMOTSParam(),
+                    -1,
+                    zero,
+                    1 << parameters.getLmsParameters()[t].getLMSigParam().getH(),
+                    zero);
+            }
+            hssKeyMaxIndex *= 1 << parameters.getLmsParameters()[t].getLMSigParam().getH();
+        }
+
+        // if this has happened we're trying to generate a really large key
+        // we'll use MAX_VALUE so that it's at least usable until someone upgrades the structure.
+        if (hssKeyMaxIndex == 0)
+        {
+            hssKeyMaxIndex = Long.MAX_VALUE;
+        }
+
+        return new HSSPrivateKeyParameters(
+            parameters.getDepth(),
+            java.util.Arrays.asList(keys),
+            java.util.Arrays.asList(sig),
+            0, hssKeyMaxIndex);
+    }
+
+    /**
+     * Increments an HSS private key without doing any work on it.
+     * HSS private keys are automatically incremented when when used to create signatures.
+     * <p>
+     * The HSS private key is ranged tested before this incrementation is applied.
+     * LMS keys will be replaced as required.
+     *
+     * @param keyPair
+     */
+    public static void incrementIndex(HSSPrivateKeyParameters keyPair)
+    {
+        synchronized (keyPair)
+        {
+            rangeTestKeys(keyPair);
+            keyPair.incIndex();
+            keyPair.getKeys().get(keyPair.getL() - 1).incIndex();
+        }
+    }
+
+
+    static void rangeTestKeys(HSSPrivateKeyParameters keyPair)
+    {
+        synchronized (keyPair)
+        {
+            if (keyPair.getIndex() >= keyPair.getIndexLimit())
+            {
+                throw new ExhaustedPrivateKeyException(
+                    "hss private key" +
+                        ((keyPair.isShard()) ? " shard" : "") +
+                        " is exhausted");
+            }
+
+
+            int L = keyPair.getL();
+            int d = L;
+            List<LMSPrivateKeyParameters> prv = keyPair.getKeys();
+            while (prv.get(d - 1).getIndex() == 1 << (prv.get(d - 1).getSigParameters().getH()))
+            {
+                d = d - 1;
+                if (d == 0)
+                {
+                    throw new ExhaustedPrivateKeyException(
+                        "hss private key" +
+                            ((keyPair.isShard()) ? " shard" : "") +
+                            " is exhausted the maximum limit for this HSS private key");
+                }
+            }
+
+
+            while (d < L)
+            {
+                keyPair.replaceConsumedKey(d);
+                d = d + 1;
+            }
+        }
+    }
+
+
+    public static HSSSignature generateSignature(HSSPrivateKeyParameters keyPair, byte[] message)
+    {
+        LMSSignedPubKey[] signed_pub_key;
+        LMSPrivateKeyParameters nextKey;
+        int L = keyPair.getL();
+
+        synchronized (keyPair)
+        {
+            rangeTestKeys(keyPair);
+
+            List<LMSPrivateKeyParameters> keys = keyPair.getKeys();
+            List<LMSSignature> sig = keyPair.getSig();
+
+            nextKey = keyPair.getKeys().get(L - 1);
+
+            // Step 2. Stand in for sig[L-1]
+            int i = 0;
+            signed_pub_key = new LMSSignedPubKey[L - 1];
+            while (i < L - 1)
+            {
+                signed_pub_key[i] = new LMSSignedPubKey(
+                    sig.get(i),
+                    keys.get(i + 1).getPublicKey());
+                i = i + 1;
+            }
+
+            //
+            // increment the index.
+            //
+            keyPair.incIndex();
+        }
+
+        LMSContext context = nextKey.generateLMSContext().withSignedPublicKeys(signed_pub_key);
+
+        context.update(message, 0, message.length);
+
+        return generateSignature(L, context);
+    }
+
+    public static HSSSignature generateSignature(int L, LMSContext context)
+    {
+        return new HSSSignature(L - 1, context.getSignedPubKeys(), LMS.generateSign(context));
+    }
+
+    public static boolean verifySignature(HSSPublicKeyParameters publicKey, HSSSignature signature, byte[] message)
+    {
+        int Nspk = signature.getlMinus1();
+        if (Nspk + 1 != publicKey.getL())
+        {
+            return false;
+        }
+
+        LMSSignature[] sigList = new LMSSignature[Nspk + 1];
+        LMSPublicKeyParameters[] pubList = new LMSPublicKeyParameters[Nspk];
+
+        for (int i = 0; i < Nspk; i++)
+        {
+            sigList[i] = signature.getSignedPubKey()[i].getSignature();
+            pubList[i] = signature.getSignedPubKey()[i].getPublicKey();
+        }
+        sigList[Nspk] = signature.getSignature();
+
+        LMSPublicKeyParameters key = publicKey.getLMSPublicKey();
+
+        for (int i = 0; i < Nspk; i++)
+        {
+            LMSSignature sig = sigList[i];
+            byte[] msg = pubList[i].toByteArray();
+            if (!LMS.verifySignature(key, sig, msg))
+            {
+                return false;
+            }
+            try
+            {
+                key = pubList[i];
+            }
+            catch (Exception ex)
+            {
+                throw new IllegalStateException(ex.getMessage(), ex);
+            }
+        }
+        return LMS.verifySignature(key, sigList[Nspk], message);
+    }
+
+
+    static class PlaceholderLMSPrivateKey
+        extends LMSPrivateKeyParameters
+    {
+
+        public PlaceholderLMSPrivateKey(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I, int maxQ, byte[] masterSecret)
+        {
+            super(lmsParameter, otsParameters, q, I, maxQ, masterSecret);
+        }
+
+        @Override
+        LMOtsPrivateKey getNextOtsPrivateKey()
+        {
+            throw new RuntimeException("placeholder only");
+        }
+
+        @Override
+        public LMSPublicKeyParameters getPublicKey()
+        {
+            throw new RuntimeException("placeholder only");
+        }
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyGenerationParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyGenerationParameters.java
new file mode 100644
index 0000000..360a9e3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyGenerationParameters.java
@@ -0,0 +1,39 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class HSSKeyGenerationParameters
+    extends KeyGenerationParameters
+{
+    private final LMSParameters[] lmsParameters;
+
+    /**
+     * Base constructor - parameters and a source of randomness.
+     *
+     * @param lmsParameters array of LMS parameters, one per level in the hierarchy (up to 8 levels).
+     * @param random   the random byte source.
+     */
+    public HSSKeyGenerationParameters(
+        LMSParameters[] lmsParameters,
+        SecureRandom random)
+    {
+        super(random, LmsUtils.calculateStrength(lmsParameters[0]));
+        if (lmsParameters.length == 0 || lmsParameters.length > 8)  // RFC 8554, Section 6.
+        {
+            throw new IllegalArgumentException("lmsParameters length should be between 1 and 8 inclusive");
+        }
+        this.lmsParameters = lmsParameters;
+    }
+
+    public int getDepth()
+    {
+        return lmsParameters.length;
+    }
+
+    public LMSParameters[] getLmsParameters()
+    {
+        return lmsParameters;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyPairGenerator.java
new file mode 100644
index 0000000..23efad3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSKeyPairGenerator.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class HSSKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    HSSKeyGenerationParameters param;
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.param = (HSSKeyGenerationParameters)param;
+    }
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        HSSPrivateKeyParameters privKey = HSS.generateHSSKeyPair(param);
+
+        return new AsymmetricCipherKeyPair(privKey.getPublicKey(), privKey);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPrivateKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPrivateKeyParameters.java
new file mode 100644
index 0000000..99e4f1c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPrivateKeyParameters.java
@@ -0,0 +1,532 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.bouncycastle.util.io.Streams;
+
+import static org.bouncycastle.pqc.crypto.lms.HSS.rangeTestKeys;
+
+public class HSSPrivateKeyParameters
+    extends LMSKeyParameters
+    implements LMSContextBasedSigner
+{
+    private final int l;
+    private final boolean isShard;
+    private List<LMSPrivateKeyParameters> keys;
+    private List<LMSSignature> sig;
+    private final long indexLimit;
+    private long index = 0;
+
+    private HSSPublicKeyParameters publicKey;
+
+    public HSSPrivateKeyParameters(int l, List<LMSPrivateKeyParameters> keys, List<LMSSignature> sig, long index, long indexLimit)
+    {
+        super(true);
+
+        this.l = l;
+        this.keys = Collections.unmodifiableList(keys);
+        this.sig = Collections.unmodifiableList(sig);
+        this.index = index;
+        this.indexLimit = indexLimit;
+        this.isShard = false;
+
+        //
+        // Correct Intermediate LMS values will be constructed during reset to index.
+        //
+        resetKeyToIndex();
+    }
+
+    private HSSPrivateKeyParameters(int l, List<LMSPrivateKeyParameters> keys, List<LMSSignature> sig, long index, long indexLimit, boolean isShard)
+    {
+        super(true);
+
+        this.l = l;
+        this.keys = Collections.unmodifiableList(keys);
+        this.sig = Collections.unmodifiableList(sig);
+        this.index = index;
+        this.indexLimit = indexLimit;
+        this.isShard = isShard;
+    }
+
+    public static HSSPrivateKeyParameters getInstance(byte[] privEnc, byte[] pubEnc)
+        throws IOException
+    {
+        HSSPrivateKeyParameters pKey = getInstance(privEnc);
+
+        pKey.publicKey = HSSPublicKeyParameters.getInstance(pubEnc);
+
+        return pKey;
+    }
+
+    public static HSSPrivateKeyParameters getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof HSSPrivateKeyParameters)
+        {
+            return (HSSPrivateKeyParameters)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            if (((DataInputStream)src).readInt() != 0)
+            {
+                throw new IllegalStateException("unknown version for hss private key");
+            }
+            int d = ((DataInputStream)src).readInt();
+            long index = ((DataInputStream)src).readLong();
+            long maxIndex = ((DataInputStream)src).readLong();
+            boolean limited = ((DataInputStream)src).readBoolean();
+
+            ArrayList<LMSPrivateKeyParameters> keys = new ArrayList<LMSPrivateKeyParameters>();
+            ArrayList<LMSSignature> signatures = new ArrayList<LMSSignature>();
+
+            for (int t = 0; t < d; t++)
+            {
+                keys.add(LMSPrivateKeyParameters.getInstance(src));
+            }
+
+            for (int t = 0; t < d - 1; t++)
+            {
+                signatures.add(LMSSignature.getInstance(src));
+            }
+
+            return new HSSPrivateKeyParameters(d, keys, signatures, index, maxIndex, limited);
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+    public int getL()
+    {
+        return l;
+    }
+
+    public synchronized long getIndex()
+    {
+        return index;
+    }
+
+    public synchronized LMSParameters[] getLMSParameters()
+    {
+        int len = keys.size();
+
+        LMSParameters[] parms = new LMSParameters[len];
+
+        for (int i = 0; i < len; i++)
+        {
+            LMSPrivateKeyParameters lmsPrivateKey = keys.get(i);
+
+            parms[i] = new LMSParameters(lmsPrivateKey.getSigParameters(), lmsPrivateKey.getOtsParameters());
+        }
+
+        return parms;
+    }
+
+    synchronized void incIndex()
+    {
+        index++;
+    }
+
+    private static HSSPrivateKeyParameters makeCopy(HSSPrivateKeyParameters privateKeyParameters)
+    {
+        try
+        {
+            return HSSPrivateKeyParameters.getInstance(privateKeyParameters.getEncoded());
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(ex.getMessage(), ex);
+        }
+    }
+
+    protected void updateHierarchy(LMSPrivateKeyParameters[] newKeys, LMSSignature[] newSig)
+    {
+        synchronized (this)
+        {
+            keys = Collections.unmodifiableList(Arrays.asList(newKeys));
+            sig = Collections.unmodifiableList(Arrays.asList(newSig));
+        }
+    }
+
+    boolean isShard()
+    {
+        return isShard;
+    }
+
+    long getIndexLimit()
+    {
+        return indexLimit;
+    }
+
+    public long getUsagesRemaining()
+    {
+        return indexLimit - index;
+    }
+
+    LMSPrivateKeyParameters getRootKey()
+    {
+        return keys.get(0);
+    }
+
+    /**
+     * Return a key that can be used usageCount times.
+     * <p>
+     * Note: this will use the range [index...index + usageCount) for the current key.
+     * </p>
+     *
+     * @param usageCount the number of usages the key should have.
+     * @return a key based on the current key that can be used usageCount times.
+     */
+    public HSSPrivateKeyParameters extractKeyShard(int usageCount)
+    {
+        synchronized (this)
+        {
+
+            if (getUsagesRemaining() < usageCount)
+            {
+                throw new IllegalArgumentException("usageCount exceeds usages remaining in current leaf");
+            }
+
+            long maxIndexForShard = index + usageCount;
+            long shardStartIndex = index;
+
+            //
+            // Move this keys index along
+            //
+            index += usageCount;
+
+            List<LMSPrivateKeyParameters> keys = new ArrayList<LMSPrivateKeyParameters>(this.getKeys());
+            List<LMSSignature> sig = new ArrayList<LMSSignature>(this.getSig());
+
+            HSSPrivateKeyParameters shard = makeCopy(new HSSPrivateKeyParameters(l, keys, sig, shardStartIndex, maxIndexForShard, true));
+
+            resetKeyToIndex();
+
+            return shard;
+        }
+    }
+
+
+    synchronized List<LMSPrivateKeyParameters> getKeys()
+    {
+        return keys;
+    }
+
+    synchronized List<LMSSignature> getSig()
+    {
+        return sig;
+    }
+
+    /**
+     * Reset to index will ensure that all LMS keys are correct for a given HSS index value.
+     * Normally LMS keys updated in sync with their parent HSS key but in cases of sharding
+     * the normal monotonic updating does not apply and the state of the LMS keys needs to be
+     * reset to match the current HSS index.
+     */
+    void resetKeyToIndex()
+    {
+        // Extract the original keys
+        List<LMSPrivateKeyParameters> originalKeys = getKeys();
+
+
+        long[] qTreePath = new long[originalKeys.size()];
+        long q = getIndex();
+
+        for (int t = originalKeys.size() - 1; t >= 0; t--)
+        {
+            LMSigParameters sigParameters = originalKeys.get(t).getSigParameters();
+            int mask = (1 << sigParameters.getH()) - 1;
+            qTreePath[t] = q & mask;
+            q >>>= sigParameters.getH();
+        }
+
+        boolean changed = false;
+        LMSPrivateKeyParameters[] keys = originalKeys.toArray(new LMSPrivateKeyParameters[originalKeys.size()]);//  new LMSPrivateKeyParameters[originalKeys.size()];
+        LMSSignature[] sig = this.sig.toArray(new LMSSignature[this.sig.size()]);//   new LMSSignature[originalKeys.size() - 1];
+
+        LMSPrivateKeyParameters originalRootKey = this.getRootKey();
+
+
+        //
+        // We need to replace the root key to a new q value.
+        //
+        if (keys[0].getIndex() - 1 != qTreePath[0])
+        {
+            keys[0] = LMS.generateKeys(
+                originalRootKey.getSigParameters(),
+                originalRootKey.getOtsParameters(),
+                (int)qTreePath[0], originalRootKey.getI(), originalRootKey.getMasterSecret());
+            changed = true;
+        }
+
+
+        for (int i = 1; i < qTreePath.length; i++)
+        {
+
+            LMSPrivateKeyParameters intermediateKey = keys[i - 1];
+
+            byte[] childI = new byte[16];
+            byte[] childSeed = new byte[32];
+            SeedDerive derive = new SeedDerive(
+                intermediateKey.getI(),
+                intermediateKey.getMasterSecret(),
+                DigestUtil.getDigest(intermediateKey.getOtsParameters().getDigestOID()));
+            derive.setQ((int)qTreePath[i - 1]);
+            derive.setJ(~1);
+
+            derive.deriveSeed(childSeed, true);
+            byte[] postImage = new byte[32];
+            derive.deriveSeed(postImage, false);
+            System.arraycopy(postImage, 0, childI, 0, childI.length);
+
+            //
+            // Q values in LMS keys post increment after they are used.
+            // For intermediate keys they will always be out by one from the derived q value (qValues[i])
+            // For the end key its value will match so no correction is required.
+            //
+            boolean lmsQMatch =
+                (i < qTreePath.length - 1) ? qTreePath[i] == keys[i].getIndex() - 1 : qTreePath[i] == keys[i].getIndex();
+
+            //
+            // Equality is I and seed being equal and the lmsQMath.
+            // I and seed are derived from this nodes parent and will change if the parent q, I, seed changes.
+            //
+            boolean seedEquals = org.bouncycastle.util.Arrays.areEqual(childI, keys[i].getI())
+                && org.bouncycastle.util.Arrays.areEqual(childSeed, keys[i].getMasterSecret());
+
+
+            if (!seedEquals)
+            {
+                //
+                // This means the parent has changed.
+                //
+                keys[i] = LMS.generateKeys(
+                    originalKeys.get(i).getSigParameters(),
+                    originalKeys.get(i).getOtsParameters(),
+                    (int)qTreePath[i], childI, childSeed);
+
+                //
+                // Ensure post increment occurs on parent and the new public key is signed.
+                //
+                sig[i - 1] = LMS.generateSign(keys[i - 1], keys[i].getPublicKey().toByteArray());
+                changed = true;
+            }
+            else if (!lmsQMatch)
+            {
+
+                //
+                // Q is different so we can generate a new private key but it will have the same public
+                // key so we do not need to sign it again.
+                //
+                keys[i] = LMS.generateKeys(
+                    originalKeys.get(i).getSigParameters(),
+                    originalKeys.get(i).getOtsParameters(),
+                    (int)qTreePath[i], childI, childSeed);
+                changed = true;
+            }
+
+        }
+
+
+        if (changed)
+        {
+            // We mutate the HSS key here!
+            updateHierarchy(keys, sig);
+        }
+
+    }
+
+    public synchronized HSSPublicKeyParameters getPublicKey()
+    {
+        return new HSSPublicKeyParameters(l, getRootKey().getPublicKey());
+    }
+
+    void replaceConsumedKey(int d)
+    {
+
+        SeedDerive deriver = keys.get(d - 1).getCurrentOTSKey().getDerivationFunction();
+        deriver.setJ(~1);
+        byte[] childRootSeed = new byte[32];
+        deriver.deriveSeed(childRootSeed, true);
+        byte[] postImage = new byte[32];
+        deriver.deriveSeed(postImage, false);
+        byte[] childI = new byte[16];
+        System.arraycopy(postImage, 0, childI, 0, childI.length);
+
+        List<LMSPrivateKeyParameters> newKeys = new ArrayList<LMSPrivateKeyParameters>(keys);
+
+        //
+        // We need the parameters from the LMS key we are replacing.
+        //
+        LMSPrivateKeyParameters oldPk = keys.get(d);
+
+
+        newKeys.set(d, LMS.generateKeys(oldPk.getSigParameters(), oldPk.getOtsParameters(), 0, childI, childRootSeed));
+
+        List<LMSSignature> newSig = new ArrayList<LMSSignature>(sig);
+
+        newSig.set(d - 1, LMS.generateSign(newKeys.get(d - 1), newKeys.get(d).getPublicKey().toByteArray()));
+
+
+        this.keys = Collections.unmodifiableList(newKeys);
+        this.sig = Collections.unmodifiableList(newSig);
+
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        HSSPrivateKeyParameters that = (HSSPrivateKeyParameters)o;
+
+        if (l != that.l)
+        {
+            return false;
+        }
+        if (isShard != that.isShard)
+        {
+            return false;
+        }
+        if (indexLimit != that.indexLimit)
+        {
+            return false;
+        }
+        if (index != that.index)
+        {
+            return false;
+        }
+        if (!keys.equals(that.keys))
+        {
+            return false;
+        }
+        return sig.equals(that.sig);
+    }
+
+    @Override
+    public synchronized byte[] getEncoded()
+        throws IOException
+    {
+        //
+        // Private keys are implementation dependent.
+        //
+
+        Composer composer = Composer.compose()
+            .u32str(0) // Version.
+            .u32str(l)
+            .u64str(index)
+            .u64str(indexLimit)
+            .bool(isShard); // Depth
+
+        for (LMSPrivateKeyParameters key : keys)
+        {
+            composer.bytes(key);
+        }
+
+        for (LMSSignature s : sig)
+        {
+            composer.bytes(s);
+        }
+
+        return composer.build();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = l;
+        result = 31 * result + (isShard ? 1 : 0);
+        result = 31 * result + keys.hashCode();
+        result = 31 * result + sig.hashCode();
+        result = 31 * result + (int)(indexLimit ^ (indexLimit >>> 32));
+        result = 31 * result + (int)(index ^ (index >>> 32));
+        return result;
+    }
+
+    @Override
+    protected Object clone()
+        throws CloneNotSupportedException
+    {
+        return makeCopy(this);
+    }
+
+    public LMSContext generateLMSContext()
+    {
+        LMSSignedPubKey[] signed_pub_key;
+        LMSPrivateKeyParameters nextKey;
+        int L = this.getL();
+
+        synchronized (this)
+        {
+            rangeTestKeys(this);
+
+            List<LMSPrivateKeyParameters> keys = this.getKeys();
+            List<LMSSignature> sig = this.getSig();
+
+            nextKey = this.getKeys().get(L - 1);
+
+            // Step 2. Stand in for sig[L-1]
+            int i = 0;
+            signed_pub_key = new LMSSignedPubKey[L - 1];
+            while (i < L - 1)
+            {
+                signed_pub_key[i] = new LMSSignedPubKey(
+                    sig.get(i),
+                    keys.get(i + 1).getPublicKey());
+                i = i + 1;
+            }
+
+            //
+            // increment the index.
+            //
+            this.incIndex();
+        }
+
+        return nextKey.generateLMSContext().withSignedPublicKeys(signed_pub_key);
+    }
+
+    public byte[] generateSignature(LMSContext context)
+    {
+        try
+        {
+            return HSS.generateSignature(getL(), context).getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode signature: " + e.getMessage(), e);
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPublicKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPublicKeyParameters.java
new file mode 100644
index 0000000..8f2ece6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSPublicKeyParameters.java
@@ -0,0 +1,150 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.io.Streams;
+
+public class HSSPublicKeyParameters
+    extends LMSKeyParameters
+    implements LMSContextBasedVerifier
+{
+    private final int l;
+    private final LMSPublicKeyParameters lmsPublicKey;
+
+    public HSSPublicKeyParameters(int l, LMSPublicKeyParameters lmsPublicKey)
+    {
+        super(false);
+
+        this.l = l;
+        this.lmsPublicKey = lmsPublicKey;
+    }
+
+    public static HSSPublicKeyParameters getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof HSSPublicKeyParameters)
+        {
+            return (HSSPublicKeyParameters)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            int L = ((DataInputStream)src).readInt();
+            LMSPublicKeyParameters lmsPublicKey = LMSPublicKeyParameters.getInstance(src);
+            return new HSSPublicKeyParameters(L, lmsPublicKey);
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null) in.close();
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+    public int getL()
+    {
+        return l;
+    }
+
+    public LMSPublicKeyParameters getLMSPublicKey()
+    {
+        return lmsPublicKey;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        HSSPublicKeyParameters publicKey = (HSSPublicKeyParameters)o;
+
+        if (l != publicKey.l)
+        {
+            return false;
+        }
+        return lmsPublicKey.equals(publicKey.lmsPublicKey);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = l;
+        result = 31 * result + lmsPublicKey.hashCode();
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return Composer.compose().u32str(l)
+            .bytes(lmsPublicKey.getEncoded())
+            .build();
+    }
+
+    public LMSContext generateLMSContext(byte[] sigEnc)
+    {
+        HSSSignature signature;
+        try
+        {
+            signature = HSSSignature.getInstance(sigEnc, getL());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("cannot parse signature: " + e.getMessage());
+        }
+
+        LMSSignedPubKey[] signedPubKeys = signature.getSignedPubKey();
+        LMSPublicKeyParameters key = signedPubKeys[signedPubKeys.length - 1].getPublicKey();
+
+        return key.generateOtsContext(signature.getSignature()).withSignedPublicKeys(signedPubKeys);
+    }
+
+    public boolean verify(LMSContext context)
+    {
+        boolean failed = false;
+
+        LMSSignedPubKey[] sigKeys = context.getSignedPubKeys();
+
+        if (sigKeys.length != getL() - 1)
+        {
+            return false;
+        }
+
+        LMSPublicKeyParameters key = getLMSPublicKey();
+
+        for (int i = 0; i < sigKeys.length; i++)
+        {
+            LMSSignature sig = sigKeys[i].getSignature();
+            byte[] msg = sigKeys[i].getPublicKey().toByteArray();
+            if (!LMS.verifySignature(key, sig, msg))
+            {
+                failed = true;
+            }
+            key = sigKeys[i].getPublicKey();
+        }
+
+        return !failed & key.verify(context);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSignature.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSignature.java
new file mode 100644
index 0000000..b9bafd9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSignature.java
@@ -0,0 +1,159 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.bouncycastle.util.Encodable;
+import org.bouncycastle.util.io.Streams;
+
+public class HSSSignature
+    implements Encodable
+{
+    private final int lMinus1;
+    private final LMSSignedPubKey[] signedPubKey;
+    private final LMSSignature signature;
+
+    public HSSSignature(int lMinus1, LMSSignedPubKey[] signedPubKey, LMSSignature signature)
+    {
+        this.lMinus1 = lMinus1;
+        this.signedPubKey = signedPubKey;
+        this.signature = signature;
+    }
+
+
+    /**
+     * @param src byte[], InputStream or HSSSignature
+     * @param L   The HSS depth, available from public key.
+     * @return An HSSSignature instance.
+     * @throws IOException
+     */
+    public static HSSSignature getInstance(Object src, int L)
+        throws IOException
+    {
+        if (src instanceof HSSSignature)
+        {
+            return (HSSSignature)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+
+            int lminus = ((DataInputStream)src).readInt();
+            if (lminus != L - 1)
+            {
+                throw new IllegalStateException("nspk exceeded maxNspk");
+            }
+            LMSSignedPubKey[] signedPubKeys = new LMSSignedPubKey[lminus];
+            if (lminus != 0)
+            {
+                for (int t = 0; t < signedPubKeys.length; t++)
+                {
+                    signedPubKeys[t] = new LMSSignedPubKey(LMSSignature.getInstance(src), LMSPublicKeyParameters.getInstance(src));
+                }
+            }
+            LMSSignature sig = LMSSignature.getInstance(src);
+
+            return new HSSSignature(lminus, signedPubKeys, sig);
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in, L);
+            }
+            finally
+            {
+               if (in != null) in.close();
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src),L);
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+
+    public int getlMinus1()
+    {
+        return lMinus1;
+    }
+
+    public LMSSignedPubKey[] getSignedPubKey()
+    {
+        return signedPubKey;
+    }
+
+    public LMSSignature getSignature()
+    {
+        return signature;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        HSSSignature signature1 = (HSSSignature)o;
+
+        if (lMinus1 != signature1.lMinus1)
+        {
+            return false;
+        }
+        // Probably incorrect - comparing Object[] arrays with Arrays.equals
+
+        if (signedPubKey.length != signature1.signedPubKey.length)
+        {
+            return false;
+        }
+
+        for (int t = 0; t < signedPubKey.length; t++)
+        {
+            if (!signedPubKey[t].equals(signature1.signedPubKey[t]))
+            {
+                return false;
+            }
+        }
+
+        return signature != null ? signature.equals(signature1.signature) : signature1.signature == null;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = lMinus1;
+        result = 31 * result + Arrays.hashCode(signedPubKey);
+        result = 31 * result + (signature != null ? signature.hashCode() : 0);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        Composer composer = Composer.compose();
+        composer.u32str(lMinus1);
+        if (signedPubKey != null)
+        {
+            for (LMSSignedPubKey sigPub : signedPubKey)
+            {
+                composer.bytes(sigPub);
+            }
+        }
+        composer.bytes(signature);
+        return composer.build();
+
+    }
+
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSigner.java
new file mode 100644
index 0000000..d3237ff
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/HSSSigner.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.crypto.MessageSigner;
+
+public class HSSSigner
+    implements MessageSigner
+{
+    private HSSPrivateKeyParameters privKey;
+    private HSSPublicKeyParameters pubKey;
+
+    public void init(boolean forSigning, CipherParameters param)
+    {
+         if (forSigning)
+         {
+             this.privKey = (HSSPrivateKeyParameters)param;
+         }
+         else
+         {
+             this.pubKey = (HSSPublicKeyParameters)param;
+         }
+    }
+
+    public byte[] generateSignature(byte[] message)
+    {
+        try
+        {
+            return HSS.generateSignature(privKey, message).getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode signature: " + e.getMessage());
+        }
+    }
+
+    public boolean verifySignature(byte[] message, byte[] signature)
+    {
+        try
+        {
+            return HSS.verifySignature(pubKey, HSSSignature.getInstance(signature, pubKey.getL()), message);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to decode signature: " + e.getMessage());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsParameters.java
new file mode 100644
index 0000000..ca2b39f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsParameters.java
@@ -0,0 +1,85 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+
+public class LMOtsParameters
+{
+    public static final int reserved = 0;
+    public static final LMOtsParameters sha256_n32_w1 = new LMOtsParameters(1, 32, 1, 265, 7, 8516, NISTObjectIdentifiers.id_sha256);
+    public static final LMOtsParameters sha256_n32_w2 = new LMOtsParameters(2, 32, 2, 133, 6, 4292, NISTObjectIdentifiers.id_sha256);
+    public static final LMOtsParameters sha256_n32_w4 = new LMOtsParameters(3, 32, 4, 67, 4, 2180, NISTObjectIdentifiers.id_sha256);
+    public static final LMOtsParameters sha256_n32_w8 = new LMOtsParameters(4, 32, 8, 34, 0, 1124, NISTObjectIdentifiers.id_sha256);
+
+    private static final Map<Object, LMOtsParameters> suppliers = new HashMap<Object, LMOtsParameters>()
+    {
+        {
+            put(sha256_n32_w1.type, sha256_n32_w1);
+            put(sha256_n32_w2.type, sha256_n32_w2);
+            put(sha256_n32_w4.type, sha256_n32_w4);
+            put(sha256_n32_w8.type, sha256_n32_w8);
+        }
+    };
+
+    private final int type;
+    private final int n;
+    private final int w;
+    private final int p;
+    private final int ls;
+    private final int sigLen;
+    private final ASN1ObjectIdentifier digestOID;
+
+    protected LMOtsParameters(int type, int n, int w, int p, int ls, int sigLen, ASN1ObjectIdentifier digestOID)
+    {
+        this.type = type;
+        this.n = n;
+        this.w = w;
+        this.p = p;
+        this.ls = ls;
+        this.sigLen = sigLen;
+        this.digestOID = digestOID;
+    }
+
+    public int getType()
+    {
+        return type;
+    }
+
+    public int getN()
+    {
+        return n;
+    }
+
+    public int getW()
+    {
+        return w;
+    }
+
+    public int getP()
+    {
+        return p;
+    }
+
+    public int getLs()
+    {
+        return ls;
+    }
+
+    public int getSigLen()
+    {
+        return sigLen;
+    }
+
+    public ASN1ObjectIdentifier getDigestOID()
+    {
+        return digestOID;
+    }
+
+    public static LMOtsParameters getParametersForType(int type)
+    {
+        return suppliers.get(type);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPrivateKey.java
new file mode 100644
index 0000000..f324fd4
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPrivateKey.java
@@ -0,0 +1,69 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+
+import static org.bouncycastle.pqc.crypto.lms.LM_OTS.D_MESG;
+import static org.bouncycastle.pqc.crypto.lms.LM_OTS.SEED_LEN;
+import static org.bouncycastle.pqc.crypto.lms.LM_OTS.SEED_RANDOMISER_INDEX;
+
+class LMOtsPrivateKey
+{
+    private final LMOtsParameters parameter;
+    private final byte[] I;
+    private final int q;
+    private final byte[] masterSecret;
+
+    public LMOtsPrivateKey(LMOtsParameters parameter, byte[] i, int q, byte[] masterSecret)
+    {
+        this.parameter = parameter;
+        I = i;
+        this.q = q;
+        this.masterSecret = masterSecret;
+    }
+
+    LMSContext getSignatureContext(LMSigParameters sigParams, byte[][] path)
+    {
+        byte[] C = new byte[SEED_LEN];
+
+        SeedDerive derive = getDerivationFunction();
+        derive.setJ(SEED_RANDOMISER_INDEX); // This value from reference impl.
+        derive.deriveSeed(C, false);
+
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+
+        LmsUtils.byteArray(this.getI(), ctx);
+        LmsUtils.u32str(this.getQ(), ctx);
+        LmsUtils.u16str(D_MESG, ctx);
+        LmsUtils.byteArray(C, ctx);
+
+        return new LMSContext(this, sigParams, ctx, C, path);
+    }
+
+    SeedDerive getDerivationFunction()
+    {
+        SeedDerive derive = new SeedDerive(I, masterSecret, DigestUtil.getDigest(parameter.getDigestOID()));
+        derive.setQ(q);
+        return derive;
+    }
+
+
+    public LMOtsParameters getParameter()
+    {
+        return parameter;
+    }
+
+    public byte[] getI()
+    {
+        return I;
+    }
+
+    public int getQ()
+    {
+        return q;
+    }
+
+    public byte[] getMasterSecret()
+    {
+        return masterSecret;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPublicKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPublicKey.java
new file mode 100644
index 0000000..2a34f5f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsPublicKey.java
@@ -0,0 +1,165 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Encodable;
+import org.bouncycastle.util.io.Streams;
+
+import static org.bouncycastle.pqc.crypto.lms.LM_OTS.D_MESG;
+
+class LMOtsPublicKey
+    implements Encodable
+{
+    private final LMOtsParameters parameter;
+    private final byte[] I;
+    private final int q;
+    private final byte[] K;
+
+
+    public LMOtsPublicKey(LMOtsParameters parameter, byte[] i, int q, byte[] k)
+    {
+        this.parameter = parameter;
+        this.I = i;
+        this.q = q;
+        this.K = k;
+    }
+
+    public static LMOtsPublicKey getInstance(Object src)
+        throws Exception
+    {
+        if (src instanceof LMOtsPublicKey)
+        {
+            return (LMOtsPublicKey)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            LMOtsParameters parameter = LMOtsParameters.getParametersForType(((DataInputStream)src).readInt());
+            byte[] I = new byte[16];
+            ((DataInputStream)src).readFully(I);
+            int q = ((DataInputStream)src).readInt();
+
+            byte[] K = new byte[parameter.getN()];
+            ((DataInputStream)src).readFully(K);
+
+            return new LMOtsPublicKey(parameter, I, q, K);
+
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null) in.close();
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+    public LMOtsParameters getParameter()
+    {
+        return parameter;
+    }
+
+    public byte[] getI()
+    {
+        return I;
+    }
+
+    public int getQ()
+    {
+        return q;
+    }
+
+    public byte[] getK()
+    {
+        return K;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMOtsPublicKey that = (LMOtsPublicKey)o;
+
+        if (q != that.q)
+        {
+            return false;
+        }
+        if (parameter != null ? !parameter.equals(that.parameter) : that.parameter != null)
+        {
+            return false;
+        }
+        if (!Arrays.equals(I, that.I))
+        {
+            return false;
+        }
+        return Arrays.equals(K, that.K);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = parameter != null ? parameter.hashCode() : 0;
+        result = 31 * result + Arrays.hashCode(I);
+        result = 31 * result + q;
+        result = 31 * result + Arrays.hashCode(K);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return Composer.compose()
+            .u32str(parameter.getType())
+            .bytes(I)
+            .u32str(q)
+            .bytes(K).build();
+    }
+
+    LMSContext createOtsContext(LMOtsSignature signature)
+    {
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+
+        LmsUtils.byteArray(I, ctx);
+        LmsUtils.u32str(q, ctx);
+        LmsUtils.u16str(D_MESG, ctx);
+        LmsUtils.byteArray(signature.getC(), ctx);
+
+        return new LMSContext(this, signature, ctx);
+    }
+
+    LMSContext createOtsContext(LMSSignature signature)
+    {
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+
+        LmsUtils.byteArray(I, ctx);
+        LmsUtils.u32str(q, ctx);
+        LmsUtils.u16str(D_MESG, ctx);
+        LmsUtils.byteArray(signature.getOtsSignature().getC(), ctx);
+
+        return new LMSContext(this, signature, ctx);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsSignature.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsSignature.java
new file mode 100644
index 0000000..786d7a7
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMOtsSignature.java
@@ -0,0 +1,129 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.bouncycastle.util.Encodable;
+import org.bouncycastle.util.io.Streams;
+
+class LMOtsSignature
+    implements Encodable
+{
+    private final LMOtsParameters type;
+    private final byte[] C;
+    private final byte[] y;
+
+    public LMOtsSignature(LMOtsParameters type, byte[] c, byte[] y)
+    {
+        this.type = type;
+        C = c;
+        this.y = y;
+    }
+
+    public static LMOtsSignature getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof LMOtsSignature)
+        {
+            return (LMOtsSignature)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+
+
+            LMOtsParameters type = LMOtsParameters.getParametersForType(((DataInputStream)src).readInt());
+            byte[] C = new byte[type.getN()];
+
+            ((DataInputStream)src).readFully(C);
+
+            byte[] sig = new byte[type.getP()*type.getN()];
+            ((DataInputStream)src).readFully(sig);
+
+
+            return new LMOtsSignature(type, C, sig);
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.4 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null) in.close();
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+
+    public LMOtsParameters getType()
+    {
+        return type;
+    }
+
+    public byte[] getC()
+    {
+        return C;
+    }
+
+    public byte[] getY()
+    {
+        return y;
+    }
+
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMOtsSignature that = (LMOtsSignature)o;
+
+        if (type != null ? !type.equals(that.type) : that.type != null)
+        {
+            return false;
+        }
+        if (!Arrays.equals(C, that.C))
+        {
+            return false;
+        }
+        return Arrays.equals(y, that.y);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = type != null ? type.hashCode() : 0;
+        result = 31 * result + Arrays.hashCode(C);
+        result = 31 * result + Arrays.hashCode(y);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return Composer.compose()
+            .u32str(type.getType())
+            .bytes(C)
+            .bytes(y)
+            .build();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMS.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMS.java
new file mode 100644
index 0000000..d084518
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMS.java
@@ -0,0 +1,142 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+
+class LMS
+{
+    static final short D_LEAF = (short)0x8282;
+    static final short D_INTR = (short)0x8383;
+
+    public static LMSPrivateKeyParameters generateKeys(LMSigParameters parameterSet, LMOtsParameters lmOtsParameters, int q, byte[] I, byte[] rootSeed)
+        throws IllegalArgumentException
+    {
+        //
+        // RFC 8554 recommends that digest used in LMS and LMOTS be of the same strength to protect against
+        // attackers going after the weaker of the two digests. This is not enforced here!
+        //
+
+        // Algorithm 5, Compute LMS private key.
+
+        // Step 1
+        // -- Parameters passed in as arguments.
+
+
+        // Step 2
+
+        if (rootSeed == null || rootSeed.length < parameterSet.getM())
+        {
+            throw new IllegalArgumentException("root seed is less than " + parameterSet.getM());
+        }
+
+        int twoToH = 1 << parameterSet.getH();
+
+        return new LMSPrivateKeyParameters(parameterSet, lmOtsParameters, q, I, twoToH, rootSeed);
+    }
+
+    public static LMSSignature generateSign(LMSPrivateKeyParameters privateKey, byte[] message)
+    {
+        //
+        // Get T from the public key.
+        // This may cause the public key to be generated.
+        //
+        // byte[][] T = new byte[privateKey.getMaxQ()][];
+
+        // Step 2
+        LMSContext context = privateKey.generateLMSContext();
+
+        context.update(message, 0, message.length);
+
+        return generateSign(context);
+    }
+
+    public static LMSSignature generateSign(LMSContext context)
+    {
+        //
+        // Get T from the public key.
+        // This may cause the public key to be generated.
+        //
+        // byte[][] T = new byte[privateKey.getMaxQ()][];
+
+        // Step 1.
+        LMOtsSignature ots_signature = LM_OTS.lm_ots_generate_signature(context.getPrivateKey(), context.getQ(), context.getC());
+
+        return new LMSSignature(context.getPrivateKey().getQ(), ots_signature, context.getSigParams(), context.getPath());
+    }
+
+//    public static boolean verifySignature(LMSPublicKeyParameters publicKey, LMSSignature S, byte[] message)
+//    {
+//        byte[] Tc = algorithm6a(S, publicKey.refI(), publicKey.getOtsParameters().getType(), message);
+//
+//        return publicKey.matchesT1(Tc);
+//    }
+
+    public static boolean verifySignature(LMSPublicKeyParameters publicKey, LMSSignature S, byte[] message)
+    {
+        LMSContext context = publicKey.generateOtsContext(S);
+
+        LmsUtils.byteArray(message, context);
+
+        return verifySignature(publicKey, context);
+    }
+
+    public static boolean verifySignature(LMSPublicKeyParameters publicKey, byte[] S, byte[] message)
+    {
+        LMSContext context = publicKey.generateLMSContext(S);
+
+        LmsUtils.byteArray(message, context);
+
+        return verifySignature(publicKey, context);
+    }
+
+    public static boolean verifySignature(LMSPublicKeyParameters publicKey, LMSContext context)
+    {
+        LMSSignature S = (LMSSignature)context.getSignature();
+        LMSigParameters lmsParameter = S.getParameter();
+        int h = lmsParameter.getH();
+        byte[][] path = S.getY();
+        byte[] Kc = LM_OTS.lm_ots_validate_signature_calculate(context);
+        // Step 4
+        // node_num = 2^h + q
+        int node_num = (1 << h) + S.getQ();
+
+        // tmp = H(I || u32str(node_num) || u16str(D_LEAF) || Kc)
+        byte[] I = publicKey.getI();
+        Digest H = DigestUtil.getDigest(lmsParameter.getDigestOID());
+        byte[] tmp = new byte[H.getDigestSize()];
+
+        H.update(I, 0, I.length);
+        LmsUtils.u32str(node_num, H);
+        LmsUtils.u16str(D_LEAF, H);
+        H.update(Kc, 0, Kc.length);
+        H.doFinal(tmp, 0);
+
+        int i = 0;
+
+        while (node_num > 1)
+        {
+            if ((node_num & 1) == 1)
+            {
+                // is odd
+                H.update(I, 0, I.length);
+                LmsUtils.u32str(node_num / 2, H);
+                LmsUtils.u16str(D_INTR, H);
+                H.update(path[i], 0, path[i].length);
+                H.update(tmp, 0, tmp.length);
+                H.doFinal(tmp, 0);
+            }
+            else
+            {
+                H.update(I, 0, I.length);
+                LmsUtils.u32str(node_num / 2, H);
+                LmsUtils.u16str(D_INTR, H);
+                H.update(tmp, 0, tmp.length);
+                H.update(path[i], 0, path[i].length);
+                H.doFinal(tmp, 0);
+            }
+            node_num = node_num / 2;
+            i++;
+        }
+        byte[] Tc = tmp;
+        return publicKey.matchesT1(Tc);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContext.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContext.java
new file mode 100644
index 0000000..33cc6ba
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContext.java
@@ -0,0 +1,124 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+
+import static org.bouncycastle.pqc.crypto.lms.LM_OTS.MAX_HASH;
+
+public class LMSContext
+    implements Digest
+{
+    private final byte[] C;
+    private final LMOtsPrivateKey key;
+    private final LMSigParameters sigParams;
+    private final byte[][] path;
+    private final LMOtsPublicKey publicKey;
+    private final Object signature;
+
+    private LMSSignedPubKey[] signedPubKeys;
+    private volatile Digest digest;
+
+    public LMSContext(LMOtsPrivateKey key, LMSigParameters sigParams, Digest digest, byte[] C, byte[][] path)
+    {
+        this.key = key;
+        this.sigParams = sigParams;
+        this.digest = digest;
+        this.C = C;
+        this.path = path;
+        this.publicKey = null;
+        this.signature = null;
+    }
+
+    public LMSContext(LMOtsPublicKey publicKey, Object signature, Digest digest)
+    {
+        this.publicKey = publicKey;
+        this.signature = signature;
+        this.digest = digest;
+        this.C = null;
+        this.key = null;
+        this.sigParams = null;
+        this.path = null;
+    }
+
+    byte[] getC()
+    {
+        return C;
+    }
+
+    byte[] getQ()
+    {
+        byte[] Q = new byte[MAX_HASH + 2];
+
+        digest.doFinal(Q, 0);
+        
+        digest = null;
+
+        return Q;
+    }
+
+    byte[][] getPath()
+    {
+        return path;
+    }
+
+    LMOtsPrivateKey getPrivateKey()
+    {
+        return key;
+    }
+
+    public LMOtsPublicKey getPublicKey()
+    {
+        return publicKey;
+    }
+
+    LMSigParameters getSigParams()
+    {
+        return sigParams;
+    }
+
+    public Object getSignature()
+    {
+        return signature;
+    }
+
+    LMSSignedPubKey[] getSignedPubKeys()
+    {
+        return signedPubKeys;
+    }
+
+    LMSContext withSignedPublicKeys(LMSSignedPubKey[] signedPubKeys)
+    {
+        this.signedPubKeys = signedPubKeys;
+
+        return this;
+    }
+
+    public String getAlgorithmName()
+    {
+        return digest.getAlgorithmName();
+    }
+
+    public int getDigestSize()
+    {
+        return digest.getDigestSize();
+    }
+
+    public void update(byte in)
+    {
+        digest.update(in);
+    }
+
+    public void update(byte[] in, int inOff, int len)
+    {
+        digest.update(in, inOff, len);
+    }
+
+    public int doFinal(byte[] out, int outOff)
+    {
+        return digest.doFinal(out, outOff);
+    }
+
+    public void reset()
+    {
+        digest.reset();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedSigner.java
new file mode 100644
index 0000000..53c5bea
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedSigner.java
@@ -0,0 +1,10 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+public interface LMSContextBasedSigner
+{
+    LMSContext generateLMSContext();
+
+    byte[] generateSignature(LMSContext context);
+
+    long getUsagesRemaining();
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedVerifier.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedVerifier.java
new file mode 100644
index 0000000..7ae1fc9
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSContextBasedVerifier.java
@@ -0,0 +1,8 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+public interface LMSContextBasedVerifier
+{
+    LMSContext generateLMSContext(byte[] signature);
+
+    boolean verify(LMSContext context);
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSException.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSException.java
new file mode 100644
index 0000000..7294e09
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSException.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+public class LMSException extends Exception
+{
+    public LMSException()
+    {
+    }
+
+    public LMSException(String message)
+    {
+        super(message);
+    }
+
+    public LMSException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    public LMSException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    public LMSException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
+    {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyGenerationParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyGenerationParameters.java
new file mode 100644
index 0000000..58ebdd2
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyGenerationParameters.java
@@ -0,0 +1,28 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class LMSKeyGenerationParameters
+    extends KeyGenerationParameters
+{
+    private final LMSParameters lmsParameters;
+
+    /**
+     * Base constructor - parameters and a source of randomness.
+     *
+     * @param lmsParameters LMS parameter set to use.
+     * @param random   the random byte source.
+     */
+    public LMSKeyGenerationParameters(LMSParameters lmsParameters, SecureRandom random)
+    {
+        super(random, LmsUtils.calculateStrength(lmsParameters)); // TODO: need something for "strength"
+        this.lmsParameters = lmsParameters;
+    }
+
+    public LMSParameters getParameters()
+    {
+        return lmsParameters;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyPairGenerator.java
new file mode 100644
index 0000000..793cbf6
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyPairGenerator.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+
+public class LMSKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
+{
+    LMSKeyGenerationParameters param;
+
+    public void init(KeyGenerationParameters param)
+    {
+        this.param = (LMSKeyGenerationParameters)param;
+    }
+
+    public AsymmetricCipherKeyPair generateKeyPair()
+    {
+        SecureRandom source = param.getRandom();
+
+        byte[] I = new byte[16];
+        source.nextBytes(I);
+
+        byte[] rootSecret = new byte[32];
+        source.nextBytes(rootSecret);
+
+        LMSPrivateKeyParameters privKey = LMS.generateKeys(param.getParameters().getLMSigParam(), param.getParameters().getLMOTSParam(), 0, I, rootSecret);
+
+        return new AsymmetricCipherKeyPair(privKey.getPublicKey(), privKey);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyParameters.java
new file mode 100644
index 0000000..81ef938
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSKeyParameters.java
@@ -0,0 +1,19 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.util.Encodable;
+
+public abstract class LMSKeyParameters
+    extends AsymmetricKeyParameter
+    implements Encodable
+{
+    protected LMSKeyParameters(boolean isPrivateKey)
+    {
+        super(isPrivateKey);
+    }
+
+    abstract public byte[] getEncoded()
+        throws IOException;
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSParameters.java
new file mode 100644
index 0000000..14bf8dc
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSParameters.java
@@ -0,0 +1,23 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+public class LMSParameters
+{
+    private final LMSigParameters lmSigParam;
+    private final LMOtsParameters lmOTSParam;
+
+    public LMSParameters(LMSigParameters lmSigParam, LMOtsParameters lmOTSParam)
+    {
+        this.lmSigParam = lmSigParam;
+        this.lmOTSParam = lmOTSParam;
+    }
+
+    public LMSigParameters getLMSigParam()
+    {
+        return lmSigParam;
+    }
+
+    public LMOtsParameters getLMOTSParam()
+    {
+        return lmOTSParam;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPrivateKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPrivateKeyParameters.java
new file mode 100644
index 0000000..1a134cb
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPrivateKeyParameters.java
@@ -0,0 +1,491 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
+
+public class LMSPrivateKeyParameters
+    extends LMSKeyParameters
+    implements LMSContextBasedSigner
+{
+    private static CacheKey T1 = new CacheKey(1);
+    private static CacheKey[] internedKeys = new CacheKey[129];
+
+    static
+    {
+        internedKeys[1] = T1;
+        for (int i = 2; i < internedKeys.length; i++)
+        {
+            internedKeys[i] = new CacheKey(i);
+        }
+    }
+
+    private final byte[] I;
+    private final LMSigParameters parameters;
+    private final LMOtsParameters otsParameters;
+    private final int maxQ;
+    private final byte[] masterSecret;
+    private final Map<CacheKey, byte[]> tCache;
+    private final int maxCacheR;
+    private final Digest tDigest;
+
+    private int q;
+
+    //
+    // These are not final because they can be generated.
+    // They also do not need to be persisted.
+    //
+    private LMSPublicKeyParameters publicKey;
+
+
+    public LMSPrivateKeyParameters(LMSigParameters lmsParameter, LMOtsParameters otsParameters, int q, byte[] I, int maxQ, byte[] masterSecret)
+    {
+        super(true);
+        this.parameters = lmsParameter;
+        this.otsParameters = otsParameters;
+        this.q = q;
+        this.I = Arrays.clone(I);
+        this.maxQ = maxQ;
+        this.masterSecret = Arrays.clone(masterSecret);
+        this.maxCacheR = 1 << (parameters.getH() + 1);
+        this.tCache = new WeakHashMap<CacheKey, byte[]>();
+        this.tDigest = DigestUtil.getDigest(lmsParameter.getDigestOID());
+    }
+
+    private LMSPrivateKeyParameters(LMSPrivateKeyParameters parent, int q, int maxQ)
+    {
+        super(true);
+        this.parameters = parent.parameters;
+        this.otsParameters = parent.otsParameters;
+        this.q = q;
+        this.I = parent.I;
+        this.maxQ = maxQ;
+        this.masterSecret = parent.masterSecret;
+        this.maxCacheR = 1 << parameters.getH();
+        this.tCache = parent.tCache;
+        this.tDigest = DigestUtil.getDigest(parameters.getDigestOID());
+        this.publicKey = parent.publicKey;
+    }
+
+    public static LMSPrivateKeyParameters getInstance(byte[] privEnc, byte[] pubEnc)
+        throws IOException
+    {
+        LMSPrivateKeyParameters pKey = getInstance(privEnc);
+    
+        pKey.publicKey = LMSPublicKeyParameters.getInstance(pubEnc);
+
+        return pKey;
+    }
+
+    public static LMSPrivateKeyParameters getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof LMSPrivateKeyParameters)
+        {
+            return (LMSPrivateKeyParameters)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            DataInputStream dIn = (DataInputStream)src;
+
+            /*
+            .u32str(0) // version
+            .u32str(parameters.getType()) // type
+            .u32str(otsParameters.getType()) // ots type
+            .bytes(I) // I at 16 bytes
+            .u32str(q) // q
+            .u32str(maxQ) // maximum q
+            .u32str(masterSecret.length) // length of master secret.
+            .bytes(masterSecret) // the master secret
+            .build();
+             */
+
+
+            if (dIn.readInt() != 0)
+            {
+                throw new IllegalStateException("expected version 0 lms private key");
+            }
+
+            LMSigParameters parameter = LMSigParameters.getParametersForType(dIn.readInt());
+            LMOtsParameters otsParameter = LMOtsParameters.getParametersForType(dIn.readInt());
+            byte[] I = new byte[16];
+            dIn.readFully(I);
+
+            int q = dIn.readInt();
+            int maxQ = dIn.readInt();
+            int l = dIn.readInt();
+            if (l < 0)
+            {
+                throw new IllegalStateException("secret length less than zero");
+            }
+            if (l > dIn.available())
+            {
+                throw new IOException("secret length exceeded " + dIn.available());
+            }
+            byte[] masterSecret = new byte[l];
+            dIn.readFully(masterSecret);
+
+            return new LMSPrivateKeyParameters(parameter, otsParameter, q, I, maxQ, masterSecret);
+
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+
+    LMOtsPrivateKey getCurrentOTSKey()
+    {
+        synchronized (this)
+        {
+            if (q >= maxQ)
+            {
+                throw new ExhaustedPrivateKeyException("ots private keys expired");
+            }
+            return new LMOtsPrivateKey(otsParameters, I, q, masterSecret);
+        }
+    }
+
+    /**
+     * Return the key index (the q value).
+     *
+     * @return private key index number.
+     */
+    public synchronized int getIndex()
+    {
+        return q;
+    }
+
+    synchronized void incIndex()
+    {
+        q++;
+    }
+
+    public LMSContext generateLMSContext()
+    {
+        // Step 1.
+        LMSigParameters lmsParameter = this.getSigParameters();
+
+        // Step 2
+        int h = lmsParameter.getH();
+        int q = getIndex();
+        LMOtsPrivateKey otsPk = getNextOtsPrivateKey();
+
+        int i = 0;
+        int r = (1 << h) + q;
+        byte[][] path = new byte[h][];
+
+        while (i < h)
+        {
+            int tmp = (r / (1 << i)) ^ 1;
+
+            path[i] = this.findT(tmp);
+            i++;
+        }
+
+        return otsPk.getSignatureContext(this.getSigParameters(), path);
+    }
+
+    public byte[] generateSignature(LMSContext context)
+    {
+        try
+        {
+            return LMS.generateSign(context).getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode signature: " + e.getMessage(), e);
+        }
+    }
+
+    LMOtsPrivateKey getNextOtsPrivateKey()
+    {
+        synchronized (this)
+        {
+            if (q >= maxQ)
+            {
+                throw new ExhaustedPrivateKeyException("ots private key exhausted");
+            }
+            LMOtsPrivateKey otsPrivateKey = new LMOtsPrivateKey(otsParameters, I, q, masterSecret);
+            incIndex();
+            return otsPrivateKey;
+        }
+    }
+
+
+    /**
+     * Return a key that can be used usageCount times.
+     * <p>
+     * Note: this will use the range [index...index + usageCount) for the current key.
+     * </p>
+     *
+     * @param usageCount the number of usages the key should have.
+     * @return a key based on the current key that can be used usageCount times.
+     */
+    public LMSPrivateKeyParameters extractKeyShard(int usageCount)
+    {
+        synchronized (this)
+        {
+            if (q + usageCount >= maxQ)
+            {
+                throw new IllegalArgumentException("usageCount exceeds usages remaining");
+            }
+            LMSPrivateKeyParameters keyParameters = new LMSPrivateKeyParameters(this, q, q + usageCount);
+            q += usageCount;
+
+            return keyParameters;
+        }
+    }
+
+    public LMSigParameters getSigParameters()
+    {
+        return parameters;
+    }
+
+    public LMOtsParameters getOtsParameters()
+    {
+        return otsParameters;
+    }
+
+    public byte[] getI()
+    {
+        return Arrays.clone(I);
+    }
+
+    public byte[] getMasterSecret()
+    {
+        return Arrays.clone(masterSecret);
+    }
+
+    public long getUsagesRemaining()
+    {
+        return maxQ - q;
+    }
+
+    public LMSPublicKeyParameters getPublicKey()
+    {
+        synchronized (this)
+        {
+            if (publicKey == null)
+            {
+                publicKey = new LMSPublicKeyParameters(parameters, otsParameters, this.findT(T1), I);
+            }
+            return publicKey;
+        }
+    }
+
+    byte[] findT(int r)
+    {
+        if (r < maxCacheR)
+        {
+            return findT(r < internedKeys.length ? internedKeys[r] : new CacheKey(r));
+        }
+
+        return calcT(r);
+    }
+
+    private byte[] findT(CacheKey key)
+    {
+        synchronized (tCache)
+        {
+            byte[] t = tCache.get(key);
+
+            if (t != null)
+            {
+                return t;
+            }
+
+            t = calcT(key.index);
+            tCache.put(key, t);
+
+            return t;
+        }
+    }
+
+    private byte[] calcT(int r)
+    {
+        int h = this.getSigParameters().getH();
+
+        int twoToh = 1 << h;
+
+        byte[] T;
+
+        // r is a base 1 index.
+
+        if (r >= twoToh)
+        {
+            LmsUtils.byteArray(this.getI(), tDigest);
+            LmsUtils.u32str(r, tDigest);
+            LmsUtils.u16str(LMS.D_LEAF, tDigest);
+            //
+            // These can be pre generated at the time of key generation and held within the private key.
+            // However it will cost memory to have them stick around.
+            //
+            byte[] K = LM_OTS.lms_ots_generatePublicKey(this.getOtsParameters(), this.getI(), (r - twoToh), this.getMasterSecret());
+
+            LmsUtils.byteArray(K, tDigest);
+            T = new byte[tDigest.getDigestSize()];
+            tDigest.doFinal(T, 0);
+            return T;
+        }
+
+        byte[] t2r = findT(2 * r);
+        byte[] t2rPlus1 = findT((2 * r + 1));
+
+        LmsUtils.byteArray(this.getI(), tDigest);
+        LmsUtils.u32str(r, tDigest);
+        LmsUtils.u16str(LMS.D_INTR, tDigest);
+        LmsUtils.byteArray(t2r, tDigest);
+        LmsUtils.byteArray(t2rPlus1, tDigest);
+        T = new byte[tDigest.getDigestSize()];
+        tDigest.doFinal(T, 0);
+
+        return T;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMSPrivateKeyParameters that = (LMSPrivateKeyParameters)o;
+
+        if (q != that.q)
+        {
+            return false;
+        }
+        if (maxQ != that.maxQ)
+        {
+            return false;
+        }
+        if (!Arrays.areEqual(I, that.I))
+        {
+            return false;
+        }
+        if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null)
+        {
+            return false;
+        }
+        if (otsParameters != null ? !otsParameters.equals(that.otsParameters) : that.otsParameters != null)
+        {
+            return false;
+        }
+        if (!Arrays.areEqual(masterSecret, that.masterSecret))
+        {
+            return false;
+        }
+
+        //
+        // Only compare public keys if they both exist.
+        // Otherwise we would trigger the creation of one or both of them
+        //
+        if (publicKey != null && that.publicKey != null)
+        {
+            return publicKey.equals(that.publicKey);
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = q;
+        result = 31 * result + Arrays.hashCode(I);
+        result = 31 * result + (parameters != null ? parameters.hashCode() : 0);
+        result = 31 * result + (otsParameters != null ? otsParameters.hashCode() : 0);
+        result = 31 * result + maxQ;
+        result = 31 * result + Arrays.hashCode(masterSecret);
+        result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        //
+        // NB there is no formal specification for the encoding of private keys.
+        // It is implementation dependent.
+        //
+        // Format:
+        //     version u32
+        //     type u32
+        //     otstype u32
+        //     I u8x16
+        //     q u32
+        //     maxQ u32
+        //     master secret Length u32
+        //     master secret u8[]
+        //
+
+        return Composer.compose()
+            .u32str(0) // version
+            .u32str(parameters.getType()) // type
+            .u32str(otsParameters.getType()) // ots type
+            .bytes(I) // I at 16 bytes
+            .u32str(q) // q
+            .u32str(maxQ) // maximum q
+            .u32str(masterSecret.length) // length of master secret.
+            .bytes(masterSecret) // the master secret
+            .build();
+    }
+
+    private static class CacheKey
+    {
+        private final int index;
+
+        CacheKey(int index)
+        {
+            this.index = index;
+        }
+
+        public int hashCode()
+        {
+            return index;
+        }
+
+        public boolean equals(Object o)
+        {
+            if (o instanceof CacheKey)
+            {
+                return ((CacheKey)o).index == this.index;
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPublicKeyParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPublicKeyParameters.java
new file mode 100644
index 0000000..03df580
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSPublicKeyParameters.java
@@ -0,0 +1,193 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.io.Streams;
+
+public class LMSPublicKeyParameters
+    extends LMSKeyParameters
+    implements LMSContextBasedVerifier
+{
+    private final LMSigParameters parameterSet;
+    private final LMOtsParameters lmOtsType;
+    private final byte[] I;
+    private final byte[] T1;
+
+    public LMSPublicKeyParameters(LMSigParameters parameterSet, LMOtsParameters lmOtsType, byte[] T1, byte[] I)
+    {
+        super(false);
+
+        this.parameterSet = parameterSet;
+        this.lmOtsType = lmOtsType;
+        this.I = Arrays.clone(I);
+        this.T1 = Arrays.clone(T1);
+    }
+
+    public static LMSPublicKeyParameters getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof LMSPublicKeyParameters)
+        {
+            return (LMSPublicKeyParameters)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            int pubType = ((DataInputStream)src).readInt();
+            LMSigParameters lmsParameter = LMSigParameters.getParametersForType(pubType);
+            LMOtsParameters ostTypeCode = LMOtsParameters.getParametersForType(((DataInputStream)src).readInt());
+
+            byte[] I = new byte[16];
+            ((DataInputStream)src).readFully(I);
+
+            byte[] T1 = new byte[lmsParameter.getM()];
+            ((DataInputStream)src).readFully(T1);
+            return new LMSPublicKeyParameters(lmsParameter, ostTypeCode, T1, I);
+        }
+        else if (src instanceof byte[])
+        {
+
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return this.toByteArray();
+    }
+
+    public LMSigParameters getSigParameters()
+    {
+        return parameterSet;
+    }
+
+    public LMOtsParameters getOtsParameters()
+    {
+        return lmOtsType;
+    }
+
+    public LMSParameters getLMSParameters()
+    {
+        return new LMSParameters(this.getSigParameters(), this.getOtsParameters());
+    }
+
+    public byte[] getT1()
+    {
+        return Arrays.clone(T1);
+    }
+
+    boolean matchesT1(byte[] sig)
+    {
+        return Arrays.constantTimeAreEqual(T1, sig);
+    }
+
+    public byte[] getI()
+    {
+        return Arrays.clone(I);
+    }
+
+    byte[] refI()
+    {
+        return I;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMSPublicKeyParameters publicKey = (LMSPublicKeyParameters)o;
+
+        if (!parameterSet.equals(publicKey.parameterSet))
+        {
+            return false;
+        }
+        if (!lmOtsType.equals(publicKey.lmOtsType))
+        {
+            return false;
+        }
+        if (!Arrays.areEqual(I, publicKey.I))
+        {
+            return false;
+        }
+        return Arrays.areEqual(T1, publicKey.T1);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = parameterSet.hashCode();
+        result = 31 * result + lmOtsType.hashCode();
+        result = 31 * result + Arrays.hashCode(I);
+        result = 31 * result + Arrays.hashCode(T1);
+        return result;
+    }
+
+    byte[] toByteArray()
+    {
+        return Composer.compose()
+            .u32str(parameterSet.getType())
+            .u32str(lmOtsType.getType())
+            .bytes(I)
+            .bytes(T1)
+            .build();
+    }
+
+    public LMSContext generateLMSContext(byte[] signature)
+    {
+        try
+        {
+            return generateOtsContext(LMSSignature.getInstance(signature));
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("cannot parse signature: " + e.getMessage());
+        }
+    }
+
+    LMSContext generateOtsContext(LMSSignature S)
+    {
+        int ots_typecode = getOtsParameters().getType();
+        if (S.getOtsSignature().getType().getType() != ots_typecode)
+        {
+            throw new IllegalArgumentException("ots type from lsm signature does not match ots" +
+                " signature type from embedded ots signature");
+        }
+
+        return new LMOtsPublicKey(LMOtsParameters.getParametersForType(ots_typecode), I,  S.getQ(), null).createOtsContext(S);
+    }
+
+    public boolean verify(LMSContext context)
+    {
+        return LMS.verifySignature(this, context);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignature.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignature.java
new file mode 100644
index 0000000..f4010f5
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignature.java
@@ -0,0 +1,140 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import org.bouncycastle.util.Encodable;
+import org.bouncycastle.util.io.Streams;
+
+class LMSSignature
+    implements Encodable
+{
+    private final int q;
+    private final LMOtsSignature otsSignature;
+    private final LMSigParameters parameter;
+    private final byte[][] y;
+
+    public LMSSignature(int q, LMOtsSignature otsSignature, LMSigParameters parameter, byte[][] y)
+    {
+        this.q = q;
+        this.otsSignature = otsSignature;
+        this.parameter = parameter;
+        this.y = y;
+    }
+
+    public static LMSSignature getInstance(Object src)
+        throws IOException
+    {
+        if (src instanceof LMSSignature)
+        {
+            return (LMSSignature)src;
+        }
+        else if (src instanceof DataInputStream)
+        {
+            int q = ((DataInputStream)src).readInt();
+            LMOtsSignature otsSignature = LMOtsSignature.getInstance(src);
+            LMSigParameters type = LMSigParameters.getParametersForType(((DataInputStream)src).readInt());
+
+            byte[][] path = new byte[type.getH()][];
+            for (int h = 0; h < path.length; h++)
+            {
+                path[h] = new byte[type.getM()];
+                ((DataInputStream)src).readFully(path[h]);
+            }
+
+            return new LMSSignature(q, otsSignature, type, path);
+        }
+        else if (src instanceof byte[])
+        {
+            InputStream in = null;
+            try // 1.5 / 1.6 compatibility
+            {
+                in = new DataInputStream(new ByteArrayInputStream((byte[])src));
+                return getInstance(in);
+            }
+            finally
+            {
+                if (in != null) in.close();
+            }
+        }
+        else if (src instanceof InputStream)
+        {
+            return getInstance(Streams.readAll((InputStream)src));
+        }
+
+        throw new IllegalArgumentException("cannot parse " + src);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMSSignature that = (LMSSignature)o;
+
+        if (q != that.q)
+        {
+            return false;
+        }
+        if (otsSignature != null ? !otsSignature.equals(that.otsSignature) : that.otsSignature != null)
+        {
+            return false;
+        }
+        if (parameter != null ? !parameter.equals(that.parameter) : that.parameter != null)
+        {
+            return false;
+        }
+        return Arrays.deepEquals(y, that.y);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = q;
+        result = 31 * result + (otsSignature != null ? otsSignature.hashCode() : 0);
+        result = 31 * result + (parameter != null ? parameter.hashCode() : 0);
+        result = 31 * result + Arrays.deepHashCode(y);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return Composer.compose()
+            .u32str(q)
+            .bytes(otsSignature.getEncoded())
+            .u32str(parameter.getType())
+            .bytes(y)
+            .build();
+    }
+
+    public int getQ()
+    {
+        return q;
+    }
+
+    public LMOtsSignature getOtsSignature()
+    {
+        return otsSignature;
+    }
+
+    public LMSigParameters getParameter()
+    {
+        return parameter;
+    }
+
+    public byte[][] getY()
+    {
+        return y;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignedPubKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignedPubKey.java
new file mode 100644
index 0000000..d8c949c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSignedPubKey.java
@@ -0,0 +1,67 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.IOException;
+
+import org.bouncycastle.util.Encodable;
+
+class LMSSignedPubKey
+    implements Encodable
+{
+    private final LMSSignature signature;
+    private final LMSPublicKeyParameters publicKey;
+
+    public LMSSignedPubKey(LMSSignature signature, LMSPublicKeyParameters publicKey)
+    {
+        this.signature = signature;
+        this.publicKey = publicKey;
+    }
+
+
+    public LMSSignature getSignature()
+    {
+        return signature;
+    }
+
+    public LMSPublicKeyParameters getPublicKey()
+    {
+        return publicKey;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o)
+        {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass())
+        {
+            return false;
+        }
+
+        LMSSignedPubKey that = (LMSSignedPubKey)o;
+
+        if (signature != null ? !signature.equals(that.signature) : that.signature != null)
+        {
+            return false;
+        }
+        return publicKey != null ? publicKey.equals(that.publicKey) : that.publicKey == null;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = signature != null ? signature.hashCode() : 0;
+        result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0);
+        return result;
+    }
+
+    public byte[] getEncoded()
+        throws IOException
+    {
+        return Composer.compose()
+            .bytes(signature.getEncoded())
+            .bytes(publicKey.getEncoded())
+            .build();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSigner.java
new file mode 100644
index 0000000..30242ab
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSSigner.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.io.IOException;
+
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.crypto.MessageSigner;
+
+public class LMSSigner
+    implements MessageSigner
+{
+    private LMSPrivateKeyParameters privKey;
+    private LMSPublicKeyParameters pubKey;
+
+    public void init(boolean forSigning, CipherParameters param)
+    {
+         if (forSigning)
+         {
+             privKey = (LMSPrivateKeyParameters)param;
+         }
+         else
+         {
+             pubKey = (LMSPublicKeyParameters)param;
+         }
+    }
+
+    public byte[] generateSignature(byte[] message)
+    {
+        try
+        {
+            return LMS.generateSign(privKey, message).getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to encode signature: " + e.getMessage());
+        }
+    }
+
+    public boolean verifySignature(byte[] message, byte[] signature)
+    {
+        try
+        {
+            return LMS.verifySignature(pubKey, LMSSignature.getInstance(signature), message);
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to decode signature: " + e.getMessage());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSigParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSigParameters.java
new file mode 100644
index 0000000..597592c
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LMSigParameters.java
@@ -0,0 +1,65 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+
+public class LMSigParameters
+{
+    public static final LMSigParameters lms_sha256_n32_h5 = new LMSigParameters(5, 32, 5, NISTObjectIdentifiers.id_sha256);
+    public static final LMSigParameters lms_sha256_n32_h10 = new LMSigParameters(6, 32, 10, NISTObjectIdentifiers.id_sha256);
+    public static final LMSigParameters lms_sha256_n32_h15 = new LMSigParameters(7, 32, 15, NISTObjectIdentifiers.id_sha256);
+    public static final LMSigParameters lms_sha256_n32_h20 = new LMSigParameters(8, 32, 20, NISTObjectIdentifiers.id_sha256);
+    public static final LMSigParameters lms_sha256_n32_h25 = new LMSigParameters(9, 32, 25, NISTObjectIdentifiers.id_sha256);
+
+    private static Map<Object, LMSigParameters> paramBuilders = new HashMap<Object, LMSigParameters>()
+    {
+        {
+            put(lms_sha256_n32_h5.type, lms_sha256_n32_h5);
+            put(lms_sha256_n32_h10.type, lms_sha256_n32_h10);
+            put(lms_sha256_n32_h15.type, lms_sha256_n32_h15);
+            put(lms_sha256_n32_h20.type, lms_sha256_n32_h20);
+            put(lms_sha256_n32_h25.type, lms_sha256_n32_h25);
+        }
+    };
+
+    private final int type;
+    private final int m;
+    private final int h;
+    private final ASN1ObjectIdentifier digestOid;
+
+    protected LMSigParameters(int type, int m, int h, ASN1ObjectIdentifier digestOid)
+    {
+        this.type = type;
+        this.m = m;
+        this.h = h;
+        this.digestOid = digestOid;
+    }
+
+    public int getType()
+    {
+        return type;
+    }
+
+    public int getH()
+    {
+        return h;
+    }
+
+    public int getM()
+    {
+        return m;
+    }
+
+    public ASN1ObjectIdentifier getDigestOID()
+    {
+        return digestOid;
+    }
+
+    static LMSigParameters getParametersForType(int type)
+    {
+        return paramBuilders.get(type);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LM_OTS.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LM_OTS.java
new file mode 100644
index 0000000..0b0d516
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LM_OTS.java
@@ -0,0 +1,256 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Pack;
+
+class LM_OTS
+{
+
+    private static final short D_PBLC = (short)0x8080;
+    private static final int ITER_K = 20;
+    private static final int ITER_PREV = 23;
+    private static final int ITER_J = 22;
+    static final int SEED_RANDOMISER_INDEX = ~2;
+    static final int SEED_LEN = 32;
+    static final int MAX_HASH = 32;
+
+    static final short D_MESG = (short)0x8181;
+
+
+    public static int coef(byte[] S, int i, int w)
+    {
+        int index = (i * w) / 8;
+        int digits_per_byte = 8 / w;
+        int shift = w * (~i & (digits_per_byte - 1));
+        int mask = (1 << w) - 1;
+
+        return (S[index] >>> shift) & mask;
+    }
+
+
+    public static int cksm(byte[] S, int sLen, LMOtsParameters parameters)
+    {
+        int sum = 0;
+
+        int w = parameters.getW();
+
+        // NB assumption about size of "w" not overflowing integer.
+        int twoWpow = (1 << w) - 1;
+
+        for (int i = 0; i < (sLen * 8 / parameters.getW()); i++)
+        {
+            sum = sum + twoWpow - coef(S, i, parameters.getW());
+        }
+        return sum << parameters.getLs();
+    }
+
+
+    public static LMOtsPublicKey lms_ots_generatePublicKey(LMOtsPrivateKey privateKey)
+    {
+        byte[] K = lms_ots_generatePublicKey(privateKey.getParameter(), privateKey.getI(), privateKey.getQ(), privateKey.getMasterSecret());
+        return new LMOtsPublicKey(privateKey.getParameter(), privateKey.getI(), privateKey.getQ(), K);
+    }
+
+    static byte[] lms_ots_generatePublicKey(LMOtsParameters parameter, byte[] I, int q, byte[] masterSecret)
+    {
+
+
+        //
+        // Start hash that computes the final value.
+        //
+        Digest publicContext = DigestUtil.getDigest(parameter.getDigestOID());
+        byte[] prehashPrefix = Composer.compose()
+            .bytes(I)
+            .u32str(q)
+            .u16str(D_PBLC)
+            .padUntil(0, 22)
+            .build();
+        publicContext.update(prehashPrefix, 0, prehashPrefix.length);
+
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+
+        byte[] buf = Composer.compose()
+            .bytes(I)
+            .u32str(q)
+            .padUntil(0, 23 + ctx.getDigestSize())
+            .build();
+
+
+        SeedDerive derive = new SeedDerive(I, masterSecret, DigestUtil.getDigest(parameter.getDigestOID()));
+        derive.setQ(q);
+        derive.setJ(0);
+
+        int p = parameter.getP();
+        int n = parameter.getN();
+        final int twoToWminus1 = (1 << parameter.getW()) - 1;
+
+
+        for (int i = 0; i < p; i++)
+        {
+            derive.deriveSeed(buf, i < p - 1, ITER_PREV); // Private Key!
+            Pack.shortToBigEndian((short)i, buf, ITER_K);
+            for (int j = 0; j < twoToWminus1; j++)
+            {
+                buf[ITER_J] = (byte)j;
+                ctx.update(buf, 0, buf.length);
+                ctx.doFinal(buf, ITER_PREV);
+            }
+            publicContext.update(buf, ITER_PREV, n);
+        }
+
+        byte[] K = new byte[publicContext.getDigestSize()];
+        publicContext.doFinal(K, 0);
+
+        return K;
+
+    }
+
+    public static LMOtsSignature lm_ots_generate_signature(LMSigParameters sigParams, LMOtsPrivateKey privateKey, byte[][] path, byte[] message, boolean preHashed)
+    {
+        //
+        // Add the randomizer.
+        //
+
+        byte[] C;
+        byte[] Q = new byte[MAX_HASH + 2];
+
+        if (!preHashed)
+        {
+            LMSContext qCtx = privateKey.getSignatureContext(sigParams, path);
+
+            LmsUtils.byteArray(message, 0, message.length, qCtx);
+
+            C = qCtx.getC();
+            Q = qCtx.getQ();
+        }
+        else
+        {
+            C = new byte[SEED_LEN];
+            System.arraycopy(message, 0, Q, 0, privateKey.getParameter().getN());
+        }
+
+        return lm_ots_generate_signature(privateKey, Q, C);
+    }
+
+    public static LMOtsSignature lm_ots_generate_signature(LMOtsPrivateKey privateKey, byte[] Q, byte[] C)
+    {
+        LMOtsParameters parameter = privateKey.getParameter();
+
+        int n = parameter.getN();
+        int p = parameter.getP();
+        int w = parameter.getW();
+
+        byte[] sigComposer = new byte[p * n];
+
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+
+        SeedDerive derive = privateKey.getDerivationFunction();
+
+        int cs = cksm(Q, n, parameter);
+        Q[n] = (byte)((cs >>> 8) & 0xFF);
+        Q[n + 1] = (byte)cs;
+
+        byte[] tmp = Composer.compose().bytes(privateKey.getI()).u32str(privateKey.getQ()).padUntil(0, ITER_PREV + n).build();
+
+        derive.setJ(0);
+        for (int i = 0; i < p; i++)
+        {
+            Pack.shortToBigEndian((short)i, tmp, ITER_K);
+            derive.deriveSeed(tmp, i < p - 1, ITER_PREV);
+            int a = coef(Q, i, w);
+            for (int j = 0; j < a; j++)
+            {
+                tmp[ITER_J] = (byte)j;
+                ctx.update(tmp, 0, ITER_PREV + n);
+                ctx.doFinal(tmp, ITER_PREV);
+            }
+            System.arraycopy(tmp, ITER_PREV, sigComposer, n * i, n);
+        }
+
+        return new LMOtsSignature(parameter, C, sigComposer);
+    }
+
+    public static boolean lm_ots_validate_signature(LMOtsPublicKey publicKey, LMOtsSignature signature, byte[] message, boolean prehashed)
+        throws LMSException
+    {
+        if (!signature.getType().equals(publicKey.getParameter()))
+        {
+            throw new LMSException("public key and signature ots types do not match");
+        }
+        return Arrays.areEqual(lm_ots_validate_signature_calculate(publicKey, signature, message), publicKey.getK());
+    }
+
+    public static byte[] lm_ots_validate_signature_calculate(LMOtsPublicKey publicKey, LMOtsSignature signature, byte[] message)
+    {
+        LMSContext ctx = publicKey.createOtsContext(signature);
+
+        LmsUtils.byteArray(message, ctx);
+
+        return lm_ots_validate_signature_calculate(ctx);
+    }
+
+    public static byte[] lm_ots_validate_signature_calculate(LMSContext context)
+    {
+        LMOtsPublicKey publicKey = context.getPublicKey();
+        LMOtsParameters parameter = publicKey.getParameter();
+        Object sig = context.getSignature();
+        LMOtsSignature signature;
+        if (sig instanceof LMSSignature)
+        {
+            signature = ((LMSSignature)sig).getOtsSignature();
+        }
+        else
+        {
+            signature = (LMOtsSignature)sig;
+        }
+
+        int n = parameter.getN();
+        int w = parameter.getW();
+        int p = parameter.getP();
+        byte[] Q = context.getQ();
+
+        int cs = cksm(Q, n, parameter);
+        Q[n] = (byte)((cs >>> 8) & 0xFF);
+        Q[n + 1] = (byte)cs;
+
+        byte[] I = publicKey.getI();
+        int    q = publicKey.getQ();
+
+        Digest finalContext = DigestUtil.getDigest(parameter.getDigestOID());
+        LmsUtils.byteArray(I, finalContext);
+        LmsUtils.u32str(q, finalContext);
+        LmsUtils.u16str(D_PBLC, finalContext);
+
+        byte[] tmp = Composer.compose()
+            .bytes(I)
+            .u32str(q)
+            .padUntil(0, ITER_PREV + n).build();
+
+        int max_digit = (1 << w) - 1;
+
+        byte[] y = signature.getY();
+
+        Digest ctx = DigestUtil.getDigest(parameter.getDigestOID());
+        for (int i = 0; i < p; i++)
+        {
+            Pack.shortToBigEndian((short)i, tmp, ITER_K);
+            System.arraycopy(y, i * n, tmp, ITER_PREV, n);
+            int a = coef(Q, i, w);
+
+            for (int j = a; j < max_digit; j++)
+            {
+                tmp[ITER_J] = (byte)j;
+                ctx.update(tmp, 0, ITER_PREV + n);
+                ctx.doFinal(tmp, ITER_PREV);
+            }
+
+            finalContext.update(tmp, ITER_PREV, n);
+        }
+
+        byte[] K = new byte[n];
+        finalContext.doFinal(K, 0);
+
+        return K;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LmsUtils.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LmsUtils.java
new file mode 100644
index 0000000..8d1addb
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/LmsUtils.java
@@ -0,0 +1,41 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+
+class LmsUtils
+{
+    static void u32str(int n, Digest d)
+    {
+        d.update((byte)(n >>> 24));
+        d.update((byte)(n >>> 16));
+        d.update((byte)(n >>> 8));
+        d.update((byte)(n));
+    }
+
+    static void u16str(short n, Digest d)
+    {
+        d.update((byte)(n >>> 8));
+        d.update((byte)(n));
+    }
+
+    static void byteArray(byte[] array, Digest digest)
+    {
+        digest.update(array, 0, array.length);
+    }
+
+    static void byteArray(byte[] array, int start, int len, Digest digest)
+    {
+        digest.update(array, start, len);
+    }
+
+    static int calculateStrength(LMSParameters lmsParameters)
+    {
+        if (lmsParameters == null)
+        {
+            throw new NullPointerException("lmsParameters cannot be null");
+        }
+
+        LMSigParameters sigParameters = lmsParameters.getLMSigParam();
+        return (1 << sigParameters.getH()) * sigParameters.getM();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/SeedDerive.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/SeedDerive.java
new file mode 100644
index 0000000..a62b917
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/lms/SeedDerive.java
@@ -0,0 +1,92 @@
+package org.bouncycastle.pqc.crypto.lms;
+
+import org.bouncycastle.crypto.Digest;
+
+class SeedDerive
+{
+    private final byte[] I;
+    private final byte[] masterSeed;
+    private final Digest digest;
+    private int q;
+    private int j;
+
+
+    public SeedDerive(byte[] I, byte[] masterSeed, Digest digest)
+    {
+        this.I = I;
+        this.masterSeed = masterSeed;
+        this.digest = digest;
+    }
+
+    public int getQ()
+    {
+        return q;
+    }
+
+    public void setQ(int q)
+    {
+        this.q = q;
+    }
+
+    public int getJ()
+    {
+        return j;
+    }
+
+    public void setJ(int j)
+    {
+        this.j = j;
+    }
+
+    public byte[] getI()
+    {
+        return I;
+    }
+
+    public byte[] getMasterSeed()
+    {
+        return masterSeed;
+    }
+
+
+    public byte[] deriveSeed(byte[] target, int offset)
+    {
+        if (target.length < digest.getDigestSize())
+        {
+            throw new IllegalArgumentException("target length is less than digest size.");
+        }
+
+        digest.update(I, 0, I.length);
+        digest.update((byte)(q >>> 24));
+        digest.update((byte)(q >>> 16));
+        digest.update((byte)(q >>> 8));
+        digest.update((byte)(q));
+
+        digest.update((byte)(j >>> 8));
+        digest.update((byte)(j));
+        digest.update((byte)0xFF);
+        digest.update(masterSeed, 0, masterSeed.length);
+
+        digest.doFinal(target, offset); // Digest resets here.
+
+        return target;
+    }
+
+    public void deriveSeed(byte[] target, boolean incJ)
+    {
+        deriveSeed(target, incJ, 0);
+    }
+
+
+    public void deriveSeed(byte[] target, boolean incJ, int offset)
+    {
+
+        deriveSeed(target, offset);
+
+        if (incJ)
+        {
+            j++;
+        }
+
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java
index b52370d..6c0f2ba 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCCA2KeyPairGenerator.java
@@ -5,7 +5,6 @@
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
-import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
 import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
@@ -54,7 +53,7 @@
      */
     private void initializeDefault()
     {
-        McElieceCCA2KeyGenerationParameters mcCCA2Params = new McElieceCCA2KeyGenerationParameters(CryptoServicesRegistrar.getSecureRandom(), new McElieceCCA2Parameters());
+        McElieceCCA2KeyGenerationParameters mcCCA2Params = new McElieceCCA2KeyGenerationParameters(null, new McElieceCCA2Parameters());
         init(mcCCA2Params);
     }
 
@@ -65,7 +64,7 @@
         this.mcElieceCCA2Params = (McElieceCCA2KeyGenerationParameters)param;
 
         // set source of randomness
-        this.random = CryptoServicesRegistrar.getSecureRandom();
+        this.random = param.getRandom();
 
         this.m = this.mcElieceCCA2Params.getParameters().getM();
         this.n = this.mcElieceCCA2Params.getParameters().getN();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCipher.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCipher.java
index 7223d45..7dd3497 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceCipher.java
@@ -105,7 +105,6 @@
 
     private void initCipherEncrypt(McEliecePublicKeyParameters pubKey)
     {
-        this.sr = sr != null ? sr : CryptoServicesRegistrar.getSecureRandom();
         n = pubKey.getN();
         k = pubKey.getK();
         t = pubKey.getT();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java
index dcd3b7d..175e4d7 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceFujisakiCipher.java
@@ -94,7 +94,6 @@
 
     private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey)
     {
-        this.sr = sr != null ? sr : CryptoServicesRegistrar.getSecureRandom();
         this.messDigest = Utils.getDigest(pubKey.getDigest());
         n = pubKey.getN();
         k = pubKey.getK();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java
index 5285045..55bd0c7 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McElieceKeyPairGenerator.java
@@ -4,7 +4,6 @@
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
-import org.bouncycastle.crypto.CryptoServicesRegistrar;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 import org.bouncycastle.pqc.math.linearalgebra.GF2Matrix;
 import org.bouncycastle.pqc.math.linearalgebra.GF2mField;
@@ -61,7 +60,7 @@
      */
     private void initializeDefault()
     {
-        McElieceKeyGenerationParameters mcParams = new McElieceKeyGenerationParameters(CryptoServicesRegistrar.getSecureRandom(), new McElieceParameters());
+        McElieceKeyGenerationParameters mcParams = new McElieceKeyGenerationParameters(null, new McElieceParameters());
         initialize(mcParams);
     }
 
@@ -69,13 +68,7 @@
         KeyGenerationParameters param)
     {
         this.mcElieceParams = (McElieceKeyGenerationParameters)param;
-
-        // set source of randomness
         this.random = param.getRandom();
-        if (this.random == null)
-        {
-            this.random = CryptoServicesRegistrar.getSecureRandom();
-        }
 
         this.m = this.mcElieceParams.getParameters().getM();
         this.n = this.mcElieceParams.getParameters().getN();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java
index 7fa02ed..f22170d 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/mceliece/McEliecePointchevalCipher.java
@@ -111,7 +111,6 @@
 
     private void initCipherEncrypt(McElieceCCA2PublicKeyParameters pubKey)
     {
-        this.sr = sr != null ? sr : CryptoServicesRegistrar.getSecureRandom();
         this.messDigest = Utils.getDigest(pubKey.getDigest());
         n = pubKey.getN();
         k = pubKey.getK();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/newhope/NHSecretKeyProcessor.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/newhope/NHSecretKeyProcessor.java
new file mode 100644
index 0000000..1934bd3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/newhope/NHSecretKeyProcessor.java
@@ -0,0 +1,146 @@
+package org.bouncycastle.pqc.crypto.newhope;
+
+import java.security.SecureRandom;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.crypto.Xof;
+import org.bouncycastle.crypto.digests.SHAKEDigest;
+import org.bouncycastle.pqc.crypto.ExchangePair;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * A processor with associated builders for doing secret key transformation using
+ * the New Hope algorithm.
+ */
+public class NHSecretKeyProcessor
+{
+    /**
+     * Party U (initiator) processor builder.
+     */
+    public static class PartyUBuilder
+    {
+        private final AsymmetricCipherKeyPair aKp;
+        private final NHAgreement agreement = new NHAgreement();
+
+        private byte[] sharedInfo = null;
+        private boolean used = false;
+
+        public PartyUBuilder(SecureRandom random)
+        {
+            NHKeyPairGenerator kpGen = new NHKeyPairGenerator();
+
+            kpGen.init(new KeyGenerationParameters(random, 2048));
+
+            aKp = kpGen.generateKeyPair();
+
+            agreement.init(aKp.getPrivate());
+        }
+
+        public PartyUBuilder withSharedInfo(byte[] sharedInfo)
+        {
+            this.sharedInfo = Arrays.clone(sharedInfo);
+
+            return this;
+        }
+
+        public byte[] getPartA()
+        {
+            return ((NHPublicKeyParameters)aKp.getPublic()).getPubData();
+        }
+
+        public NHSecretKeyProcessor build(byte[] partB)
+        {
+            if (used)
+            {
+                throw new IllegalStateException("builder already used");
+            }
+
+            used = true;
+
+            return new NHSecretKeyProcessor(agreement.calculateAgreement(new NHPublicKeyParameters(partB)), sharedInfo);
+        }
+    }
+
+    /**
+     * Party V (responder) processor builder.
+     */
+    public static class PartyVBuilder
+    {
+        protected final SecureRandom random;
+
+        private byte[] sharedInfo = null;
+        private byte[] sharedSecret = null;
+        private boolean used = false;
+
+        public PartyVBuilder(SecureRandom random)
+        {
+            this.random = random;
+        }
+
+        public PartyVBuilder withSharedInfo(byte[] sharedInfo)
+        {
+            this.sharedInfo = Arrays.clone(sharedInfo);
+
+            return this;
+        }
+
+        public byte[] getPartB(byte[] partUContribution)
+        {
+            NHExchangePairGenerator exchGen = new NHExchangePairGenerator(random);
+
+            ExchangePair bEp = exchGen.generateExchange(new NHPublicKeyParameters(partUContribution));
+
+            sharedSecret = bEp.getSharedValue();
+
+            return ((NHPublicKeyParameters)bEp.getPublicKey()).getPubData();
+        }
+
+        public NHSecretKeyProcessor build()
+        {
+            if (used)
+            {
+                throw new IllegalStateException("builder already used");
+            }
+
+            used = true;
+
+            return new NHSecretKeyProcessor(sharedSecret, sharedInfo);
+        }
+    }
+
+    private final Xof xof = new SHAKEDigest(256);
+
+    private NHSecretKeyProcessor(byte[] secret, byte[] shared)
+    {
+        xof.update(secret, 0, secret.length);
+
+        if (shared != null)
+        {
+            xof.update(shared, 0, shared.length);
+        }
+
+        Arrays.fill(secret, (byte)0);
+    }
+
+    public byte[] processKey(byte[] initialKey)
+    {
+        byte[] xorBytes = new byte[initialKey.length];
+
+        xof.doFinal(xorBytes, 0, xorBytes.length);
+
+        xor(initialKey, xorBytes);
+
+        Arrays.fill(xorBytes, (byte)0);
+
+        return initialKey;
+    }
+
+    private static void xor(byte[] a, byte[] b)
+    {
+        for (int i = 0; i != a.length; i++)
+        {
+            a[i] ^= b[i];
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java
index e050fcb..aaafae5 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUEncryptionKeyGenerationParameters.java
@@ -5,6 +5,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.SecureRandom;
 import java.util.Arrays;
 
 import org.bouncycastle.crypto.CryptoServicesRegistrar;
@@ -22,36 +23,43 @@
 {
     /**
      * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for key size.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters EES1087EP2 = new NTRUEncryptionKeyGenerationParameters(1087, 2048, 120, 120, 256, 13, 25, 14, true, new byte[]{0, 6, 3}, true, false, new SHA512Digest());
 
     /**
      * A conservative (in terms of security) parameter set that gives 256 bits of security and is a tradeoff between key size and encryption/decryption speed.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters EES1171EP1 = new NTRUEncryptionKeyGenerationParameters(1171, 2048, 106, 106, 256, 13, 20, 15, true, new byte[]{0, 6, 4}, true, false, new SHA512Digest());
 
     /**
      * A conservative (in terms of security) parameter set that gives 256 bits of security and is optimized for encryption/decryption speed.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters EES1499EP1 = new NTRUEncryptionKeyGenerationParameters(1499, 2048, 79, 79, 256, 13, 17, 19, true, new byte[]{0, 6, 5}, true, false, new SHA512Digest());
 
     /**
      * A parameter set that gives 128 bits of security and uses simple ternary polynomials.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters APR2011_439 = new NTRUEncryptionKeyGenerationParameters(439, 2048, 146, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, false, new SHA256Digest());
 
     /**
      * Like <code>APR2011_439</code>, this parameter set gives 128 bits of security but uses product-form polynomials and <code>f=1+pF</code>.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters APR2011_439_FAST = new NTRUEncryptionKeyGenerationParameters(439, 2048, 9, 8, 5, 130, 128, 9, 32, 9, true, new byte[]{0, 7, 101}, true, true, new SHA256Digest());
 
     /**
      * A parameter set that gives 256 bits of security and uses simple ternary polynomials.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters APR2011_743 = new NTRUEncryptionKeyGenerationParameters(743, 2048, 248, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, false, new SHA512Digest());
 
     /**
      * Like <code>APR2011_743</code>, this parameter set gives 256 bits of security but uses product-form polynomials and <code>f=1+pF</code>.
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source (but the value present at class load time).
      */
     public static final NTRUEncryptionKeyGenerationParameters APR2011_743_FAST = new NTRUEncryptionKeyGenerationParameters(743, 2048, 11, 11, 15, 220, 256, 10, 27, 14, true, new byte[]{0, 7, 105}, false, true, new SHA512Digest());
 
@@ -80,13 +88,12 @@
 
     /**
      * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>).
-     *
      * @param N            number of polynomial coefficients
      * @param q            modulus
      * @param df           number of ones in the private polynomial <code>f</code>
      * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
      * @param db           number of random bits to prepend to the message
-     * @param c            a parameter for the Index Generation Function ({@link org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param c            a parameter for the Index Generation Function ({@link IndexGenerator})
      * @param minCallsR    minimum number of hash calls for the IGF to make
      * @param minCallsMask minimum number of calls to generate the masking polynomial
      * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
@@ -94,10 +101,11 @@
      * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
      * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
      * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     * @param random       entropy source, if <code>null</code> uses {@link CryptoServicesRegistrar#getSecureRandom()}
      */
-    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg, SecureRandom random)
     {
-        super(CryptoServicesRegistrar.getSecureRandom(), db);
+        super(null != random ? random : CryptoServicesRegistrar.getSecureRandom(), db);
         this.N = N;
         this.q = q;
         this.df = df;
@@ -116,7 +124,72 @@
     }
 
     /**
+     * Constructs a parameter set that uses ternary private keys (i.e. <code>polyType=SIMPLE</code>).
+     *
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df           number of ones in the private polynomial <code>f</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link org.bouncycastle.pqc.crypto.ntru.IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>. The <code>MessageDigest</code> must support the <code>getDigestLength()</code> method.
+     */
+    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
+    {
+        this(N, q, df, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg, null);
+    }
+
+    /**
      * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>).
+     * @param N            number of polynomial coefficients
+     * @param q            modulus
+     * @param df1          number of ones in the private polynomial <code>f1</code>
+     * @param df2          number of ones in the private polynomial <code>f2</code>
+     * @param df3          number of ones in the private polynomial <code>f3</code>
+     * @param dm0          minimum acceptable number of -1's, 0's, and 1's in the polynomial <code>m'</code> in the last encryption step
+     * @param db           number of random bits to prepend to the message
+     * @param c            a parameter for the Index Generation Function ({@link IndexGenerator})
+     * @param minCallsR    minimum number of hash calls for the IGF to make
+     * @param minCallsMask minimum number of calls to generate the masking polynomial
+     * @param hashSeed     whether to hash the seed in the MGF first (true) or use the seed directly (false)
+     * @param oid          three bytes that uniquely identify the parameter set
+     * @param sparse       whether to treat ternary polynomials as sparsely populated ({@link org.bouncycastle.pqc.math.ntru.polynomial.SparseTernaryPolynomial} vs {@link org.bouncycastle.pqc.math.ntru.polynomial.DenseTernaryPolynomial})
+     * @param fastFp       whether <code>f=1+p*F</code> for a ternary <code>F</code> (true) or <code>f</code> is ternary (false)
+     * @param hashAlg      a valid identifier for a <code>java.security.MessageDigest</code> instance such as <code>SHA-256</code>
+     * @param random       entropy source, if <code>null</code> uses {@link CryptoServicesRegistrar#getSecureRandom()}
+     */
+    public NTRUEncryptionKeyGenerationParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg, SecureRandom random)
+    {
+        super(null != random ? random : CryptoServicesRegistrar.getSecureRandom(), db);
+
+        this.N = N;
+        this.q = q;
+        this.df1 = df1;
+        this.df2 = df2;
+        this.df3 = df3;
+        this.db = db;
+        this.dm0 = dm0;
+        this.c = c;
+        this.minCallsR = minCallsR;
+        this.minCallsMask = minCallsMask;
+        this.hashSeed = hashSeed;
+        this.oid = oid;
+        this.sparse = sparse;
+        this.fastFp = fastFp;
+        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT;
+        this.hashAlg = hashAlg;
+        init();
+    }
+
+    /**
+     * Constructs a parameter set that uses product-form private keys (i.e. <code>polyType=PRODUCT</code>).
+     * Uses {@link CryptoServicesRegistrar#getSecureRandom()} as an entropy source.
      *
      * @param N            number of polynomial coefficients
      * @param q            modulus
@@ -136,25 +209,7 @@
      */
     public NTRUEncryptionKeyGenerationParameters(int N, int q, int df1, int df2, int df3, int dm0, int db, int c, int minCallsR, int minCallsMask, boolean hashSeed, byte[] oid, boolean sparse, boolean fastFp, Digest hashAlg)
     {
-        super(CryptoServicesRegistrar.getSecureRandom(), db);
-
-        this.N = N;
-        this.q = q;
-        this.df1 = df1;
-        this.df2 = df2;
-        this.df3 = df3;
-        this.db = db;
-        this.dm0 = dm0;
-        this.c = c;
-        this.minCallsR = minCallsR;
-        this.minCallsMask = minCallsMask;
-        this.hashSeed = hashSeed;
-        this.oid = oid;
-        this.sparse = sparse;
-        this.fastFp = fastFp;
-        this.polyType = NTRUParameters.TERNARY_POLYNOMIAL_TYPE_PRODUCT;
-        this.hashAlg = hashAlg;
-        init();
+        this(N, q, df1, df2, df3, dm0, db, c, minCallsR, minCallsMask, hashSeed, oid, sparse, fastFp, hashAlg, null);
     }
 
     private void init()
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java
index 19bf802..e7ddae2 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/ntru/NTRUSigner.java
@@ -10,8 +10,8 @@
 /**
 * Signs, verifies data and generates key pairs.
 * @deprecated the NTRUSigner algorithm was broken in 2012 by Ducas and Nguyen. See
-* <a href="http://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf">
-* http://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf</a>
+* <a href="https://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf">
+* https://www.di.ens.fr/~ducas/NTRUSign_Cryptanalysis/DucasNguyen_Learning.pdf</a>
 * for details.
 */
 public class NTRUSigner
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla1p.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla1p.java
index 861660f..e33c6f9 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla1p.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla1p.java
@@ -8,8 +8,8 @@
 class QTesla1p
 {
     private static final int PARAM_N = 1024;
-    private static final int PARAM_N_LOG = 10;
-    private static final double PARAM_SIGMA = 8.5;
+//    private static final int PARAM_N_LOG = 10;
+//    private static final double PARAM_SIGMA = 8.5;
     private static final int PARAM_Q = 343576577;
     private static final int PARAM_Q_LOG = 29;
     private static final long PARAM_QINV = 2205847551L;
@@ -19,7 +19,7 @@
     private static final int PARAM_B_BITS = 19;
     private static final int PARAM_S_BITS = 8;
     private static final int PARAM_K = 4;
-    private static final double PARAM_SIGMA_E = PARAM_SIGMA;
+//    private static final double PARAM_SIGMA_E = PARAM_SIGMA;
     private static final int PARAM_H = 25;
     private static final int PARAM_D = 22;
     private static final int PARAM_GEN_A = 108;
@@ -28,30 +28,27 @@
     private static final int PARAM_KEYGEN_BOUND_S = 554;
     private static final int PARAM_S = PARAM_KEYGEN_BOUND_S;
     private static final int PARAM_R2_INVN = 13632409;
-    private static final int PARAM_R = 172048372;
+//    private static final int PARAM_R = 172048372;
 
     private static final int CRYPTO_RANDOMBYTES = 32;
     private static final int CRYPTO_SEEDBYTES = 32;
     private static final int CRYPTO_C_BYTES = 32;
-    private static final int HM_BYTES = 64;
+    private static final int HM_BYTES = 40;
 
-    private static final int RADIX = 32;
+//    private static final int RADIX = 32;
     private static final int RADIX32 = 32;
 
 
-    static final int CRYPTO_BYTES = ((PARAM_N * (PARAM_B_BITS + 1) + 7) / 8 + CRYPTO_C_BYTES);
+    // Contains signature (z,c). z is a polynomial bounded by B, c is the output of a hashed string
+    static final int CRYPTO_BYTES = (PARAM_N * (PARAM_B_BITS + 1) + 7) / 8 + CRYPTO_C_BYTES;
     // Contains polynomial s and e, and seeds seed_a and seed_y
-    static final int CRYPTO_SECRETKEYBYTES = (1 * PARAM_N + 1 * PARAM_N * PARAM_K + 2 * CRYPTO_SEEDBYTES);
-
+    static final int CRYPTO_SECRETKEYBYTES = (PARAM_K + 1) * PARAM_S_BITS * PARAM_N / 8 + 2 * CRYPTO_SEEDBYTES + HM_BYTES;
     // Contains seed_a and polynomials t
-    static final int CRYPTO_PUBLICKEYBYTES = ((PARAM_Q_LOG * PARAM_N * PARAM_K + 7) / 8 + CRYPTO_SEEDBYTES);
+    static final int CRYPTO_PUBLICKEYBYTES = (PARAM_K * PARAM_Q_LOG * PARAM_N + 7) / 8 + CRYPTO_SEEDBYTES;
 
 
-    static int generateKeyPair(
-
-        byte[] publicKey, byte[] privateKey, SecureRandom secureRandom)
+    static int generateKeyPair(byte[] publicKey, byte[] privateKey, SecureRandom secureRandom)
     {
-
         /* Initialize Domain Separator for Error Polynomial and Secret Polynomial */
         int nonce = 0;
 
@@ -60,37 +57,35 @@
         /* Extend Random Bytes to Seed Generation of Error Polynomial and Secret Polynomial */
         byte[] randomnessExtended = new byte[(PARAM_K + 3) * CRYPTO_SEEDBYTES];
 
-        long[] secretPolynomial = new long[PARAM_N];
-        long[] errorPolynomial = new long[PARAM_N * PARAM_K];
-        long[] A = new long[PARAM_N * PARAM_K];
-        long[] T = new long[PARAM_N * PARAM_K];
+        int[] secretPolynomial = new int[PARAM_N];
+        int[] errorPolynomial = new int[PARAM_N * PARAM_K];
+        int[] A = new int[PARAM_N * PARAM_K];
+        int[] T = new int[PARAM_N * PARAM_K];
 
-        long[] s_ntt = new long[PARAM_N];
+        int[] s_ntt = new int[PARAM_N];
 
         /* Get randomnessExtended <- seedErrorPolynomial, seedSecretPolynomial, seedA, seedY */
         // this.rng.randomByte (randomness, (short) 0, Polynomial.RANDOM);
         secureRandom.nextBytes(randomness);
 
-
-        HashUtils.secureHashAlgorithmKECCAK128(randomnessExtended, 0, (PARAM_K + 3) * CRYPTO_SEEDBYTES, randomness, 0, CRYPTO_RANDOMBYTES);
-
+        HashUtils.secureHashAlgorithmKECCAK128(
+            randomnessExtended, 0, (PARAM_K + 3) * CRYPTO_SEEDBYTES,
+            randomness, 0, CRYPTO_RANDOMBYTES);
 
         /*
          * Sample the Error Polynomial Fulfilling the Criteria
          * Choose All Error Polynomial in R with Entries from D_SIGMA
          * Repeat Step at Iteration if the h Largest Entries of Error Polynomial Summation to L_E
          */
-
         for (int k = 0; k < PARAM_K; k++)
         {
             do
             {
-                Gaussian.sample_gauss_polly(++nonce, randomnessExtended, k * CRYPTO_SEEDBYTES, errorPolynomial, k * PARAM_N);
+                Gaussian.sample_gauss_poly(++nonce, randomnessExtended, k * CRYPTO_SEEDBYTES, errorPolynomial, k * PARAM_N);
             }
             while (checkPolynomial(errorPolynomial, k * PARAM_N, PARAM_KEYGEN_BOUND_E));
         }
 
-
         /*
          * Sample the Secret Polynomial Fulfilling the Criteria
          * Choose Secret Polynomial in R with Entries from D_SIGMA
@@ -98,81 +93,67 @@
          */
         do
         {
-
-            Gaussian.sample_gauss_polly(++nonce, randomnessExtended, PARAM_K * CRYPTO_SEEDBYTES, secretPolynomial, 0);
-
-            //Sample.polynomialGaussSamplerI(secretPolynomial, 0, randomnessExtended, Polynomial.SEED, ++nonce);
+            Gaussian.sample_gauss_poly(++nonce, randomnessExtended, PARAM_K * CRYPTO_SEEDBYTES, secretPolynomial, 0);
         }
         while (checkPolynomial(secretPolynomial, 0, PARAM_KEYGEN_BOUND_S));
 
-
         QTesla1PPolynomial.poly_uniform(A, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
-
         QTesla1PPolynomial.poly_ntt(s_ntt, secretPolynomial);
 
-
         for (int k = 0; k < PARAM_K; k++)
         {
             QTesla1PPolynomial.poly_mul(T, k * PARAM_N, A, k * PARAM_N, s_ntt);
             QTesla1PPolynomial.poly_add_correct(T, k * PARAM_N, T, k * PARAM_N, errorPolynomial, k * PARAM_N);
         }
 
-
         /* Pack Public and Private Keys */
-
         encodePublicKey(publicKey, T, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
-        encodePrivateKey(privateKey, secretPolynomial, errorPolynomial, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
+        encodePrivateKey(privateKey, secretPolynomial, errorPolynomial, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES, publicKey);
 
         return 0;
-
     }
 
-
-    static int generateSignature(
-
-        byte[] signature,
-        final byte[] message, int messageOffset, int messageLength,
-        final byte[] privateKey, SecureRandom secureRandom
-    )
+    static int generateSignature(byte[] signature, byte[] message, int messageOffset, int messageLength,
+        byte[] privateKey, SecureRandom secureRandom)
     {
         byte[] c = new byte[CRYPTO_C_BYTES];
         byte[] randomness = new byte[CRYPTO_SEEDBYTES];
-        byte[] randomness_input = new byte[CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES];
+        byte[] randomness_input = new byte[CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES + 2 * HM_BYTES];
         int[] pos_list = new int[PARAM_H];
         short[] sign_list = new short[PARAM_H];
-        long[] y = new long[PARAM_N];
+        int[] y = new int[PARAM_N];
 
-        long[] y_ntt = new long[PARAM_N];
-        long[] Sc = new long[PARAM_N];
-        long[] z = new long[PARAM_N];
+        int[] y_ntt = new int[PARAM_N];
+        int[] Sc = new int[PARAM_N];
+        int[] z = new int[PARAM_N];
 
-        long[] v = new long[PARAM_N * PARAM_K];
-        long[] Ec = new long[PARAM_N * PARAM_K];
-        long[] a = new long[PARAM_N * PARAM_K];
+        int[] v = new int[PARAM_N * PARAM_K];
+        int[] Ec = new int[PARAM_N * PARAM_K];
+        int[] a = new int[PARAM_N * PARAM_K];
 
         int k;
         int nonce = 0;  // Initialize domain separator for sampling y
         boolean rsp = false;
 
-        //  randombytes(randomness_input + CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
-        byte[] temporaryRandomnessInput = new byte[CRYPTO_RANDOMBYTES];
-        secureRandom.nextBytes(temporaryRandomnessInput);
-        System.arraycopy(temporaryRandomnessInput, 0, randomness_input, CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
-        // --
+        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES - CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_SEEDBYTES);
 
-
-        //  memcpy(randomness_input, &sk[CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES], CRYPTO_SEEDBYTES);
-        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_SEEDBYTES);
-        // --
+        {
+            byte[] tmp = new byte[CRYPTO_RANDOMBYTES];
+            secureRandom.nextBytes(tmp);
+            System.arraycopy(tmp, 0, randomness_input, CRYPTO_SEEDBYTES, CRYPTO_RANDOMBYTES);
+        }
 
         HashUtils.secureHashAlgorithmKECCAK128(
-            randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES, HM_BYTES, message, 0, messageLength);
+            randomness_input, CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES, HM_BYTES,
+            message, 0, messageLength);
 
         HashUtils.secureHashAlgorithmKECCAK128(
-            randomness, 0, CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES);
+            randomness, 0, CRYPTO_SEEDBYTES,
+            randomness_input, 0, randomness_input.length - HM_BYTES);
 
+        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES, randomness_input, randomness_input.length - HM_BYTES, HM_BYTES);
 
-        QTesla1PPolynomial.poly_uniform(a, privateKey, CRYPTO_SECRETKEYBYTES - 2 * CRYPTO_SEEDBYTES);
+        QTesla1PPolynomial.poly_uniform(a, privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES - 2 * CRYPTO_SEEDBYTES);
 
         while (true)
         {
@@ -184,10 +165,10 @@
                 QTesla1PPolynomial.poly_mul(v, k * PARAM_N, a, k * PARAM_N, y_ntt);
             }
 
-            hashFunction(c, 0, v, randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES);
+            hashFunction(c, 0, v, randomness_input, CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES);
             encodeC(pos_list, sign_list, c, 0);
 
-            QTesla1PPolynomial.sparse_mul8(Sc, privateKey, pos_list, sign_list);
+            QTesla1PPolynomial.sparse_mul8(Sc, 0, privateKey, 0, pos_list, sign_list);
 
             QTesla1PPolynomial.poly_add(z, y, Sc);
 
@@ -211,53 +192,50 @@
                 continue;
             }
 
-
             encodeSignature(signature, 0, c, 0, z);
             return 0;
-
         }
-
-        // return 0;
     }
 
-
-    static int verifying(
-
-        byte[] message,
-        final byte[] signature, int signatureOffset, int signatureLength,
+    static int verifying(byte[] message, final byte[] signature, int signatureOffset, int signatureLength,
         final byte[] publicKey)
     {
-
         byte c[] = new byte[CRYPTO_C_BYTES];
         byte c_sig[] = new byte[CRYPTO_C_BYTES];
         byte seed[] = new byte[CRYPTO_SEEDBYTES];
-        byte hm[] = new byte[HM_BYTES];
+        byte hm[] = new byte[2 * HM_BYTES];
         int pos_list[] = new int[PARAM_H];
         short sign_list[] = new short[PARAM_H];
         int pk_t[] = new int[PARAM_N * PARAM_K];
-        long[] w = new long[PARAM_N * PARAM_K];
-        long[] a = new long[PARAM_N * PARAM_K];
-        long[] Tc = new long[PARAM_N * PARAM_K];
+        int[] w = new int[PARAM_N * PARAM_K];
+        int[] a = new int[PARAM_N * PARAM_K];
+        int[] Tc = new int[PARAM_N * PARAM_K];
 
-        long[] z = new long[PARAM_N];
-        long[] z_ntt = new long[PARAM_N];
+        int[] z = new int[PARAM_N];
+        int[] z_ntt = new int[PARAM_N];
 
         int k = 0;
 
-        if (signatureLength < CRYPTO_BYTES)
+        if (signatureLength != CRYPTO_BYTES)
         {
             return -1;
         }
 
         decodeSignature(c, z, signature, signatureOffset);
-
         if (testZ(z))
         {
             return -2;
         }
-
-
         decodePublicKey(pk_t, seed, 0, publicKey);
+
+        // Get H(m) and hash_pk
+        HashUtils.secureHashAlgorithmKECCAK128(
+            hm, 0, HM_BYTES,
+            message, 0, message.length);
+        HashUtils.secureHashAlgorithmKECCAK128(
+            hm, HM_BYTES, HM_BYTES,
+            publicKey, 0, CRYPTO_PUBLICKEYBYTES - CRYPTO_SEEDBYTES);
+
         QTesla1PPolynomial.poly_uniform(a, seed, 0);
         encodeC(pos_list, sign_list, c, 0);
         QTesla1PPolynomial.poly_ntt(z_ntt, z);
@@ -266,12 +244,8 @@
         {      // Compute w = az - tc
             QTesla1PPolynomial.sparse_mul32(Tc, k * PARAM_N, pk_t, (k * PARAM_N), pos_list, sign_list);
             QTesla1PPolynomial.poly_mul(w, k * PARAM_N, a, k * PARAM_N, z_ntt);
-            QTesla1PPolynomial.poly_sub(w, k * PARAM_N, w, k * PARAM_N, Tc, k * PARAM_N);
+            QTesla1PPolynomial.poly_sub_reduce(w, k * PARAM_N, w, k * PARAM_N, Tc, k * PARAM_N);
         }
-
-        HashUtils.secureHashAlgorithmKECCAK128(
-            hm, 0, HM_BYTES, message, 0, message.length
-        );
         hashFunction(c_sig, 0, w, hm, 0);
 
         if (!memoryEqual(c, 0, c_sig, 0, CRYPTO_C_BYTES))
@@ -282,10 +256,9 @@
         return 0;
     }
 
-
-    static void encodePrivateKey(byte[] privateKey, final long[] secretPolynomial, final long[] errorPolynomial, final byte[] seed, int seedOffset)
+    static void encodePrivateKey(byte[] privateKey, int[] secretPolynomial, int[] errorPolynomial,
+        byte[] seed, int seedOffset, byte[] publicKey)
     {
-
         int i, k = 0;
         int skPtr = 0;
 
@@ -293,67 +266,70 @@
         {
             privateKey[skPtr + i] = (byte)secretPolynomial[i];
         }
-
         skPtr += PARAM_N;
+
         for (k = 0; k < PARAM_K; k++)
         {
             for (i = 0; i < PARAM_N; i++)
             {
                 privateKey[skPtr + (k * PARAM_N + i)] = (byte)errorPolynomial[k * PARAM_N + i];
-                //  System.out.printf("%d,   %x\n", skPtr + (k * PARAM_N + i), privateKey[skPtr + (k * PARAM_N + i)]);
             }
         }
+        skPtr += PARAM_K * PARAM_N;
 
-        System.arraycopy(seed, seedOffset, privateKey, skPtr + (PARAM_K * PARAM_N), CRYPTO_SEEDBYTES * 2);
+        System.arraycopy(seed, seedOffset, privateKey, skPtr, CRYPTO_SEEDBYTES * 2);
+        skPtr += CRYPTO_SEEDBYTES * 2;
 
+        /* Hash of the public key */
+        HashUtils.secureHashAlgorithmKECCAK128(
+            privateKey, skPtr, HM_BYTES,
+            publicKey, 0, CRYPTO_PUBLICKEYBYTES - CRYPTO_SEEDBYTES);
+        skPtr += HM_BYTES;
+
+//        assert CRYPTO_SECRETKEYBYTES == skPtr;
     }
 
-
-    static void encodePublicKey(byte[] publicKey, final long[] T, final byte[] seedA, int seedAOffset)
+    static void encodePublicKey(byte[] publicKey, final int[] T, final byte[] seedA, int seedAOffset)
     {
-
         int j = 0;
 
-
         for (int i = 0; i < (PARAM_N * PARAM_K * PARAM_Q_LOG / 32); i += PARAM_Q_LOG)
         {
-            at(publicKey, i, 0, (int)(T[j] | (T[j + 1] << 29)));
-            at(publicKey, i, 1, (int)((T[j + 1] >> 3) | (T[j + 2] << 26)));
-            at(publicKey, i, 2, (int)((T[j + 2] >> 6) | (T[j + 3] << 23)));
-            at(publicKey, i, 3, (int)((T[j + 3] >> 9) | (T[j + 4] << 20)));
-            at(publicKey, i, 4, (int)((T[j + 4] >> 12) | (T[j + 5] << 17)));
-            at(publicKey, i, 5, (int)((T[j + 5] >> 15) | (T[j + 6] << 14)));
-            at(publicKey, i, 6, (int)((T[j + 6] >> 18) | (T[j + 7] << 11)));
-            at(publicKey, i, 7, (int)((T[j + 7] >> 21) | (T[j + 8] << 8)));
-            at(publicKey, i, 8, (int)((T[j + 8] >> 24) | (T[j + 9] << 5)));
-            at(publicKey, i, 9, (int)((T[j + 9] >> 27) | (T[j + 10] << 2) | (T[j + 11] << 31)));
-            at(publicKey, i, 10, (int)((T[j + 11] >> 1) | (T[j + 12] << 28)));
-            at(publicKey, i, 11, (int)((T[j + 12] >> 4) | (T[j + 13] << 25)));
-            at(publicKey, i, 12, (int)((T[j + 13] >> 7) | (T[j + 14] << 22)));
-            at(publicKey, i, 13, (int)((T[j + 14] >> 10) | (T[j + 15] << 19)));
-            at(publicKey, i, 14, (int)((T[j + 15] >> 13) | (T[j + 16] << 16)));
-            at(publicKey, i, 15, (int)((T[j + 16] >> 16) | (T[j + 17] << 13)));
-            at(publicKey, i, 16, (int)((T[j + 17] >> 19) | (T[j + 18] << 10)));
-            at(publicKey, i, 17, (int)((T[j + 18] >> 22) | (T[j + 19] << 7)));
-            at(publicKey, i, 18, (int)((T[j + 19] >> 25) | (T[j + 20] << 4)));
-            at(publicKey, i, 19, (int)((T[j + 20] >> 28) | (T[j + 21] << 1) | (T[j + 22] << 30)));
-            at(publicKey, i, 20, (int)((T[j + 22] >> 2) | (T[j + 23] << 27)));
-            at(publicKey, i, 21, (int)((T[j + 23] >> 5) | (T[j + 24] << 24)));
-            at(publicKey, i, 22, (int)((T[j + 24] >> 8) | (T[j + 25] << 21)));
-            at(publicKey, i, 23, (int)((T[j + 25] >> 11) | (T[j + 26] << 18)));
-            at(publicKey, i, 24, (int)((T[j + 26] >> 14) | (T[j + 27] << 15)));
-            at(publicKey, i, 25, (int)((T[j + 27] >> 17) | (T[j + 28] << 12)));
-            at(publicKey, i, 26, (int)((T[j + 28] >> 20) | (T[j + 29] << 9)));
-            at(publicKey, i, 27, (int)((T[j + 29] >> 23) | (T[j + 30] << 6)));
-            at(publicKey, i, 28, (int)((T[j + 30] >> 26) | (T[j + 31] << 3)));
+            at(publicKey, i, 0, T[j] | (T[j + 1] << 29));
+            at(publicKey, i, 1, (T[j + 1] >> 3) | (T[j + 2] << 26));
+            at(publicKey, i, 2, (T[j + 2] >> 6) | (T[j + 3] << 23));
+            at(publicKey, i, 3, (T[j + 3] >> 9) | (T[j + 4] << 20));
+            at(publicKey, i, 4, (T[j + 4] >> 12) | (T[j + 5] << 17));
+            at(publicKey, i, 5, (T[j + 5] >> 15) | (T[j + 6] << 14));
+            at(publicKey, i, 6, (T[j + 6] >> 18) | (T[j + 7] << 11));
+            at(publicKey, i, 7, (T[j + 7] >> 21) | (T[j + 8] << 8));
+            at(publicKey, i, 8, (T[j + 8] >> 24) | (T[j + 9] << 5));
+            at(publicKey, i, 9, (T[j + 9] >> 27) | (T[j + 10] << 2) | (T[j + 11] << 31));
+            at(publicKey, i, 10, (T[j + 11] >> 1) | (T[j + 12] << 28));
+            at(publicKey, i, 11, (T[j + 12] >> 4) | (T[j + 13] << 25));
+            at(publicKey, i, 12, (T[j + 13] >> 7) | (T[j + 14] << 22));
+            at(publicKey, i, 13, (T[j + 14] >> 10) | (T[j + 15] << 19));
+            at(publicKey, i, 14, (T[j + 15] >> 13) | (T[j + 16] << 16));
+            at(publicKey, i, 15, (T[j + 16] >> 16) | (T[j + 17] << 13));
+            at(publicKey, i, 16, (T[j + 17] >> 19) | (T[j + 18] << 10));
+            at(publicKey, i, 17, (T[j + 18] >> 22) | (T[j + 19] << 7));
+            at(publicKey, i, 18, (T[j + 19] >> 25) | (T[j + 20] << 4));
+            at(publicKey, i, 19, (T[j + 20] >> 28) | (T[j + 21] << 1) | (T[j + 22] << 30));
+            at(publicKey, i, 20, (T[j + 22] >> 2) | (T[j + 23] << 27));
+            at(publicKey, i, 21, (T[j + 23] >> 5) | (T[j + 24] << 24));
+            at(publicKey, i, 22, (T[j + 24] >> 8) | (T[j + 25] << 21));
+            at(publicKey, i, 23, (T[j + 25] >> 11) | (T[j + 26] << 18));
+            at(publicKey, i, 24, (T[j + 26] >> 14) | (T[j + 27] << 15));
+            at(publicKey, i, 25, (T[j + 27] >> 17) | (T[j + 28] << 12));
+            at(publicKey, i, 26, (T[j + 28] >> 20) | (T[j + 29] << 9));
+            at(publicKey, i, 27, (T[j + 29] >> 23) | (T[j + 30] << 6));
+            at(publicKey, i, 28, (T[j + 30] >> 26) | (T[j + 31] << 3));
             j += 32;
         }
 
         System.arraycopy(seedA, seedAOffset, publicKey, PARAM_N * PARAM_K * PARAM_Q_LOG / 8, CRYPTO_SEEDBYTES);
-
     }
 
-
     static void decodePublicKey(int[] publicKey, byte[] seedA, int seedAOffset, final byte[] publicKeyInput)
     {
 
@@ -404,93 +380,90 @@
 
     }
 
-    private static boolean testZ(long[] Z)
+    private static boolean testZ(int[] Z)
     {
         // Returns false if valid, otherwise outputs 1 if invalid (rejected)
 
         for (int i = 0; i < PARAM_N; i++)
         {
-
             if ((Z[i] < -(PARAM_B - PARAM_S)) || (Z[i] > PARAM_B - PARAM_S))
             {
-
                 return true;
-
             }
-
         }
-
         return false;
-
     }
 
-
     private static final int maskb1 = ((1 << (PARAM_B_BITS + 1)) - 1);
 
-    static void encodeSignature(byte[] signature, int signatureOffset, byte[] C, int cOffset, long[] Z)
+    static void encodeSignature(byte[] signature, int signatureOffset, byte[] C, int cOffset, int[] Z)
     {
         int j = 0;
 
         for (int i = 0; i < (PARAM_N * (PARAM_B_BITS + 1) / 32); i += 10)
         {
-            at(signature, i, 0, (int)((Z[j] & ((1 << 20) - 1)) | (Z[j + 1] << 20)));
-            at(signature, i, 1, (int)(((Z[j + 1] >>> 12) & ((1 << 8) - 1)) | ((Z[j + 2] & maskb1) << 8) | (Z[j + 3] << 28)));
-            at(signature, i, 2, (int)(((Z[j + 3] >>> 4) & ((1 << 16) - 1)) | (Z[j + 4] << 16)));
-            at(signature, i, 3, (int)(((Z[j + 4] >>> 16) & ((1 << 4) - 1)) | ((Z[j + 5] & maskb1) << 4) | (Z[j + 6] << 24)));
-            at(signature, i, 4, (int)(((Z[j + 6] >>> 8) & ((1 << 12) - 1)) | (Z[j + 7] << 12)));
-            at(signature, i, 5, (int)((Z[j + 8] & ((1 << 20) - 1)) | (Z[j + 9] << 20)));
-            at(signature, i, 6, (int)(((Z[j + 9] >>> 12) & ((1 << 8) - 1)) | ((Z[j + 10] & maskb1) << 8) | (Z[j + 11] << 28)));
-            at(signature, i, 7, (int)(((Z[j + 11] >>> 4) & ((1 << 16) - 1)) | (Z[j + 12] << 16)));
-            at(signature, i, 8, (int)(((Z[j + 12] >>> 16) & ((1 << 4) - 1)) | ((Z[j + 13] & maskb1) << 4) | (Z[j + 14] << 24)));
-            at(signature, i, 9, (int)(((Z[j + 14] >>> 8) & ((1 << 12) - 1)) | (Z[j + 15] << 12)));
+            at(signature, i, 0, (Z[j] & ((1 << 20) - 1)) | (Z[j + 1] << 20));
+            at(signature, i, 1, ((Z[j + 1] >>> 12) & ((1 << 8) - 1)) | ((Z[j + 2] & maskb1) << 8) | (Z[j + 3] << 28));
+            at(signature, i, 2, ((Z[j + 3] >>> 4) & ((1 << 16) - 1)) | (Z[j + 4] << 16));
+            at(signature, i, 3, ((Z[j + 4] >>> 16) & ((1 << 4) - 1)) | ((Z[j + 5] & maskb1) << 4) | (Z[j + 6] << 24));
+            at(signature, i, 4, ((Z[j + 6] >>> 8) & ((1 << 12) - 1)) | (Z[j + 7] << 12));
+            at(signature, i, 5, (Z[j + 8] & ((1 << 20) - 1)) | (Z[j + 9] << 20));
+            at(signature, i, 6, ((Z[j + 9] >>> 12) & ((1 << 8) - 1)) | ((Z[j + 10] & maskb1) << 8) | (Z[j + 11] << 28));
+            at(signature, i, 7, ((Z[j + 11] >>> 4) & ((1 << 16) - 1)) | (Z[j + 12] << 16));
+            at(signature, i, 8, ((Z[j + 12] >>> 16) & ((1 << 4) - 1)) | ((Z[j + 13] & maskb1) << 4) | (Z[j + 14] << 24));
+            at(signature, i, 9, ((Z[j + 14] >>> 8) & ((1 << 12) - 1)) | (Z[j + 15] << 12));
             j += 16;
         }
 
         System.arraycopy(C, cOffset, signature, signatureOffset + PARAM_N * (PARAM_B_BITS + 1) / 8, CRYPTO_C_BYTES);
-
     }
 
-
-    static void decodeSignature(byte[] C, long[] Z, final byte[] signature, int signatureOffset)
+    static void decodeSignature(byte[] C, int[] Z, final byte[] signature, int signatureOffset)
     {
-
         int j = 0;
         for (int i = 0; i < PARAM_N; i += 16)
         {
-            Z[i] = (at(signature, j, 0) << 12) >> 12;
-            Z[i + 1] = (at(signature, j, 0) >>> 20) | ((at(signature, j, 1) << 24) >> 12);
-            Z[i + 2] = ((at(signature, j, 1) << 4) >> 12);
-            Z[i + 3] = (at(signature, j, 1) >>> 28) | ((at(signature, j, 2) << 16) >> 12);
-            Z[i + 4] = (at(signature, j, 2) >>> 16) | ((at(signature, j, 3) << 28) >> 12);
-            Z[i + 5] = (at(signature, j, 3) << 8) >> 12;
-            Z[i + 6] = (at(signature, j, 3) >>> 24) | ((at(signature, j, 4) << 20) >> 12);
-            Z[i + 7] = at(signature, j, 4) >> 12;
-            Z[i + 8] = (at(signature, j, 5) << 12) >> 12;
-            Z[i + 9] = (at(signature, j, 5) >>> 20) | ((at(signature, j, 6) << 24) >> 12);
-            Z[i + 10] = (at(signature, j, 6) << 4) >> 12;
-            Z[i + 11] = (at(signature, j, 6) >>> 28) | ((at(signature, j, 7) << 16) >> 12);
-            Z[i + 12] = (at(signature, j, 7) >>> 16) | ((at(signature, j, 8) << 28) >> 12);
-            Z[i + 13] = (at(signature, j, 8) << 8) >> 12;
-            Z[i + 14] = (at(signature, j, 8) >>> 24) | ((at(signature, j, 9) << 20) >> 12);
-            Z[i + 15] = (at(signature, j, 9) >> 12);
+            int s0 = at(signature, j, 0);
+            int s1 = at(signature, j, 1);
+            int s2 = at(signature, j, 2);
+            int s3 = at(signature, j, 3);
+            int s4 = at(signature, j, 4);
+            int s5 = at(signature, j, 5);
+            int s6 = at(signature, j, 6);
+            int s7 = at(signature, j, 7);
+            int s8 = at(signature, j, 8);
+            int s9 = at(signature, j, 9);
+
+            Z[i] = (s0 << 12) >> 12;
+            Z[i + 1] = (s0 >>> 20) | ((s1 << 24) >> 12);
+            Z[i + 2] = ((s1 << 4) >> 12);
+            Z[i + 3] = (s1 >>> 28) | ((s2 << 16) >> 12);
+            Z[i + 4] = (s2 >>> 16) | ((s3 << 28) >> 12);
+            Z[i + 5] = (s3 << 8) >> 12;
+            Z[i + 6] = (s3 >>> 24) | ((s4 << 20) >> 12);
+            Z[i + 7] = s4 >> 12;
+            Z[i + 8] = (s5 << 12) >> 12;
+            Z[i + 9] = (s5 >>> 20) | ((s6 << 24) >> 12);
+            Z[i + 10] = (s6 << 4) >> 12;
+            Z[i + 11] = (s6 >>> 28) | ((s7 << 16) >> 12);
+            Z[i + 12] = (s7 >>> 16) | ((s8 << 28) >> 12);
+            Z[i + 13] = (s8 << 8) >> 12;
+            Z[i + 14] = (s8 >>> 24) | ((s9 << 20) >> 12);
+            Z[i + 15] = (s9 >> 12);
             j += 10;
         }
         System.arraycopy(signature, signatureOffset + PARAM_N * (PARAM_B_BITS + 1) / 8, C, 0, CRYPTO_C_BYTES);
-
-
     }
 
-
     static void encodeC(int[] positionList, short[] signList, byte[] output, int outputOffset)
     {
-
         int count = 0;
         int position;
         short domainSeparator = 0;
         short[] C = new short[PARAM_N];
         byte[] randomness = new byte[HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE];
 
-        /* Use the Hash Value as Key to Generate Some Randomness */
+        // Enc: the XOF is instantiated with cSHAKE128 (see Algorithm 14).
         HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
             randomness, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE,
             domainSeparator++,
@@ -505,10 +478,9 @@
          */
         for (int i = 0; i < PARAM_H; )
         {
-
             if (count > HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE - 3)
             {
-
+                // Enc: the XOF is instantiated with cSHAKE128 (see Algorithm 14).
                 HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                     randomness, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE,
                     domainSeparator++,
@@ -516,7 +488,6 @@
                 );
 
                 count = 0;
-
             }
 
             position = (randomness[count] << 8) | (randomness[count + 1] & 0xFF);
@@ -548,26 +519,22 @@
             }
 
             count += 3;
-
         }
-
     }
 
-
-    private static void hashFunction(byte[] output, int outputOffset, long[] v, final byte[] message, int messageOffset) //, int n, int d, int q)
+    private static void hashFunction(byte[] output, int outputOffset, int[] v, byte[] message, int messageOffset)
     {
-
         int mask;
         int cL;
 
-        byte[] T = new byte[PARAM_K * PARAM_N + HM_BYTES];
+        byte[] T = new byte[PARAM_K * PARAM_N + 2 * HM_BYTES];
 
         for (int k = 0; k < PARAM_K; k++)
         {
             int index = k * PARAM_N;
             for (int i = 0; i < PARAM_N; i++)
             {
-                int temp = (int)v[index];
+                int temp = v[index];
                 // If v[i] > PARAM_Q/2 then v[i] -= PARAM_Q
                 mask = (PARAM_Q / 2 - temp) >> (RADIX32 - 1);
                 temp = ((temp - PARAM_Q) & mask) | (temp & ~mask);
@@ -579,13 +546,14 @@
                 T[index++] = (byte)((temp - cL) >> PARAM_D);
             }
         }
-        System.arraycopy(message, messageOffset, T, PARAM_N * PARAM_K, HM_BYTES);
-        HashUtils.secureHashAlgorithmKECCAK128(output, outputOffset, CRYPTO_C_BYTES, T, 0, PARAM_K * PARAM_N + HM_BYTES);
+        System.arraycopy(message, messageOffset, T, PARAM_N * PARAM_K, 2 * HM_BYTES);
 
+        HashUtils.secureHashAlgorithmKECCAK128(
+            output, outputOffset, CRYPTO_C_BYTES,
+            T, 0, T.length);
     }
 
-
-    static int lE24BitToInt(byte[] bs, int off)
+    static int littleEndianToInt24(byte[] bs, int off)
     {
         int n = bs[off] & 0xff;
         n |= (bs[++off] & 0xff) << 8;
@@ -593,12 +561,10 @@
         return n;
     }
 
-
     private static int NBLOCKS_SHAKE = HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE / (((PARAM_B_BITS + 1) + 7) / 8);
     private static int BPLUS1BYTES = ((PARAM_B_BITS + 1) + 7) / 8;
 
-
-    static void sample_y(long[] y, byte[] seed, int seedOffset, int nonce)
+    static void sample_y(int[] y, byte[] seed, int seedOffset, int nonce)
     { // Sample polynomial y, such that each coefficient is in the range [-B,B]
         int i = 0, pos = 0, nblocks = PARAM_N;
         byte buf[] = new byte[PARAM_N * BPLUS1BYTES+1];
@@ -609,7 +575,6 @@
             buf, 0, PARAM_N * nbytes, dmsp++, seed, seedOffset, CRYPTO_RANDOMBYTES
         );
 
-
         while (i < PARAM_N)
         {
             if (pos >= nblocks * nbytes)
@@ -620,7 +585,7 @@
                 );
                 pos = 0;
             }
-            y[i] = lE24BitToInt(buf, pos) & ((1 << (PARAM_B_BITS + 1)) - 1);
+            y[i] = littleEndianToInt24(buf, pos) & ((1 << (PARAM_B_BITS + 1)) - 1);
             y[i] -= PARAM_B;
             if (y[i] != (1 << PARAM_B_BITS))
             {
@@ -630,25 +595,17 @@
         }
     }
 
-
     private static void at(byte[] bs, int base, int index, int value)
     {
-        org.bouncycastle.util.Pack.intToLittleEndian(value, bs, (base * 4) + (index * 4));
+        Pack.intToLittleEndian(value, bs, (base + index) << 2);
     }
 
     private static int at(byte[] bs, int base, int index)
     {
-        int off = (base * 4) + (index * 4);
-
-        int n = bs[off] & 0xff;
-        n |= (bs[++off] & 0xff) << 8;
-        n |= (bs[++off] & 0xff) << 16;
-        n |= bs[++off] << 24;
-        return n;
+        return Pack.littleEndianToInt(bs, (base + index) << 2);
     }
 
-
-    static boolean test_correctness(long[] v, int vpos)
+    static boolean test_correctness(int[] v, int vpos)
     { // Check bounds for w = v - ec during signature verification. Returns 0 if valid, otherwise outputs 1 if invalid (rejected).
         // This function leaks the position of the coefficient that fails the test (but this is independent of the secret data).
         // It does not leak the sign of the coefficients.
@@ -658,16 +615,17 @@
         for (int i = 0; i < PARAM_N; i++)
         {
             // If v[i] > PARAM_Q/2 then v[i] -= PARAM_Q
-            mask = (int)(PARAM_Q / 2 - v[vpos + i]) >> (RADIX32 - 1);
-            val = (int)(((v[vpos + i] - PARAM_Q) & mask) | (v[vpos + i] & ~mask));
+            int a = v[vpos + i];
+            mask = (PARAM_Q / 2 - a) >> (RADIX32 - 1);
+            val =  ((a - PARAM_Q) & mask) | (a & ~mask);
             // If (Abs(val) < PARAM_Q/2 - PARAM_E) then t0 = 0, else t0 = 1
-            t0 = (int)(~(absolute(val) - (PARAM_Q / 2 - PARAM_E))) >>> (RADIX32 - 1);
+            t0 = (~(absolute(val) - (PARAM_Q / 2 - PARAM_E))) >>> (RADIX32 - 1);
 
             left = val;
             val = (val + (1 << (PARAM_D - 1)) - 1) >> PARAM_D;
             val = left - (val << PARAM_D);
             // If (Abs(val) < (1<<(PARAM_D-1))-PARAM_E) then t1 = 0, else t1 = 1
-            t1 = (int)(~(absolute(val) - ((1 << (PARAM_D - 1)) - PARAM_E))) >>> (RADIX32 - 1);
+            t1 = (~(absolute(val) - ((1 << (PARAM_D - 1)) - PARAM_E))) >>> (RADIX32 - 1);
 
             if ((t0 | t1) == 1)  // Returns 1 if any of the two tests failed
             {
@@ -677,43 +635,29 @@
         return false;
     }
 
-
-    private static boolean testRejection(long[] Z) //, int n, int b, int u)
+    private static boolean testRejection(int[] Z)
     {
-
         int valid = 0;
 
         for (int i = 0; i < PARAM_N; i++)
         {
             valid |= (PARAM_B - PARAM_S) - absolute(Z[i]);
-
         }
 
-        return (valid >>> 31) > 0;
-
+        return (valid >>> 31) != 0;
     }
 
     private static int absolute(int value)
     {
-
-        return ((value >> RADIX32 - 1) ^ value) - (value >> RADIX32 - 1);
-
+        int sign = value >> (RADIX32 - 1);
+        return (sign ^ value) - sign;
     }
 
-    private static long absolute(long value)
+    private static boolean checkPolynomial(int[] polynomial, int polyOffset, int bound)
     {
-
-        return ((value >> 63) ^ value) - (value >> 63);
-
-    }
-
-
-    private static boolean checkPolynomial(long[] polynomial, int polyOffset, int bound)
-    {
-
         int i, j, sum = 0, limit = PARAM_N;
-        long temp, mask;
-        long[] list = new long[PARAM_N];
+        int temp, mask;
+        int[] list = new int[PARAM_N];
 
         for (j = 0; j < PARAM_N; j++)
         {
@@ -724,17 +668,18 @@
         {
             for (i = 0; i < limit - 1; i++)
             {
+                int a = list[i], b = list[i + 1];
                 // If list[i+1] > list[i] then exchange contents
-                mask = (list[i + 1] - list[i]) >> (RADIX32 - 1);
-                temp = (list[i + 1] & mask) | (list[i] & ~mask);
-                list[i + 1] = (list[i] & mask) | (list[i + 1] & ~mask);
+                mask = (b - a) >> (RADIX32 - 1);
+                temp = (b & mask) | (a & ~mask);
+                list[i + 1] = (a & mask) | (b & ~mask);
                 list[i] = temp;
             }
             sum += list[limit - 1];
             limit -= 1;
         }
 
-        return (sum > bound);
+        return sum > bound;
     }
 
     static boolean memoryEqual(byte[] left, int leftOffset, byte[] right, int rightOffset, int length)
@@ -767,102 +712,96 @@
 
     }
 
-
     // End of outer.
 
     static class Gaussian
     {
-
         private static final int CDT_ROWS = 78;
         private static final int CDT_COLS = 2;
         private static final int CHUNK_SIZE = 512;
 
-        private static final long[] cdt_v = new long[]{
-            0x00000000L, 0x00000000L, // 0
-            0x0601F22AL, 0x280663D4L, // 1
-            0x11F09FFAL, 0x162FE23DL, // 2
-            0x1DA089E9L, 0x437226E8L, // 3
-            0x28EAB25DL, 0x04C51FE2L, // 4
-            0x33AC2F26L, 0x14FDBA70L, // 5
-            0x3DC767DCL, 0x4565C960L, // 6
-            0x4724FC62L, 0x3342C78AL, // 7
-            0x4FB448F4L, 0x5229D06DL, // 8
-            0x576B8599L, 0x7423407FL, // 9
-            0x5E4786DAL, 0x3210BAF7L, // 10
-            0x644B2C92L, 0x431B3947L, // 11
-            0x697E90CEL, 0x77C362C4L, // 12
-            0x6DEE0B96L, 0x2798C9CEL, // 13
-            0x71A92144L, 0x5765FCE4L, // 14
-            0x74C16FD5L, 0x1E2A0990L, // 15
-            0x7749AC92L, 0x0DF36EEBL, // 16
-            0x7954BFA4L, 0x28079289L, // 17
-            0x7AF5067AL, 0x2EDC2050L, // 18
-            0x7C3BC17CL, 0x123D5E7BL, // 19
-            0x7D38AD76L, 0x2A9381D9L, // 20
-            0x7DF9C5DFL, 0x0E868CA7L, // 21
-            0x7E8B2ABAL, 0x18E5C811L, // 22
-            0x7EF7237CL, 0x00908272L, // 23
-            0x7F4637C5L, 0x6DBA5126L, // 24
-            0x7F7F5707L, 0x4A52EDEBL, // 25
-            0x7FA808CCL, 0x23290599L, // 26
-            0x7FC4A083L, 0x69BDF2D5L, // 27
-            0x7FD870CAL, 0x42275558L, // 28
-            0x7FE5FB5DL, 0x3EF82C1BL, // 29
-            0x7FEF1BFAL, 0x6C03A362L, // 30
-            0x7FF52D4EL, 0x316C2C8CL, // 31
-            0x7FF927BAL, 0x12AE54AFL, // 32
-            0x7FFBBA43L, 0x749CC0E2L, // 33
-            0x7FFD5E3DL, 0x4524AD91L, // 34
-            0x7FFE6664L, 0x535785B5L, // 35
-            0x7FFF0A41L, 0x0B291681L, // 36
-            0x7FFF6E81L, 0x132C3D6FL, // 37
-            0x7FFFAAFEL, 0x4DBC6BEDL, // 38
-            0x7FFFCEFDL, 0x7A1E2D14L, // 39
-            0x7FFFE41EL, 0x4C6EC115L, // 40
-            0x7FFFF059L, 0x319503C8L, // 41
-            0x7FFFF754L, 0x5DDD0D40L, // 42
-            0x7FFFFB43L, 0x0B9E9823L, // 43
-            0x7FFFFD71L, 0x76B81AE1L, // 44
-            0x7FFFFEA3L, 0x7E66A1ECL, // 45
-            0x7FFFFF49L, 0x26F6E191L, // 46
-            0x7FFFFFA1L, 0x2FA31694L, // 47
-            0x7FFFFFCFL, 0x5247BEC9L, // 48
-            0x7FFFFFE7L, 0x4F4127C7L, // 49
-            0x7FFFFFF3L, 0x6FAA69FDL, // 50
-            0x7FFFFFFAL, 0x0630D073L, // 51
-            0x7FFFFFFDL, 0x0F2957BBL, // 52
-            0x7FFFFFFEL, 0x4FD29432L, // 53
-            0x7FFFFFFFL, 0x2CFAD60DL, // 54
-            0x7FFFFFFFL, 0x5967A930L, // 55
-            0x7FFFFFFFL, 0x6E4C9DFFL, // 56
-            0x7FFFFFFFL, 0x77FDCCC8L, // 57
-            0x7FFFFFFFL, 0x7C6CE89EL, // 58
-            0x7FFFFFFFL, 0x7E6D116FL, // 59
-            0x7FFFFFFFL, 0x7F50FA31L, // 60
-            0x7FFFFFFFL, 0x7FB50089L, // 61
-            0x7FFFFFFFL, 0x7FE04C2DL, // 62
-            0x7FFFFFFFL, 0x7FF2C7C1L, // 63
-            0x7FFFFFFFL, 0x7FFA8FE3L, // 64
-            0x7FFFFFFFL, 0x7FFDCB1BL, // 65
-            0x7FFFFFFFL, 0x7FFF1DE2L, // 66
-            0x7FFFFFFFL, 0x7FFFA6B7L, // 67
-            0x7FFFFFFFL, 0x7FFFDD39L, // 68
-            0x7FFFFFFFL, 0x7FFFF2A3L, // 69
-            0x7FFFFFFFL, 0x7FFFFAEFL, // 70
-            0x7FFFFFFFL, 0x7FFFFE1BL, // 71
-            0x7FFFFFFFL, 0x7FFFFF4DL, // 72
-            0x7FFFFFFFL, 0x7FFFFFBFL, // 73
-            0x7FFFFFFFL, 0x7FFFFFE9L, // 74
-            0x7FFFFFFFL, 0x7FFFFFF8L, // 75
-            0x7FFFFFFFL, 0x7FFFFFFDL, // 76
-            0x7FFFFFFFL, 0x7FFFFFFFL, // 77
+        private static final int[] cdt_v = new int[]{
+            0x00000000, 0x00000000, // 0
+            0x0601F22A, 0x280663D4, // 1
+            0x11F09FFA, 0x162FE23D, // 2
+            0x1DA089E9, 0x437226E8, // 3
+            0x28EAB25D, 0x04C51FE2, // 4
+            0x33AC2F26, 0x14FDBA70, // 5
+            0x3DC767DC, 0x4565C960, // 6
+            0x4724FC62, 0x3342C78A, // 7
+            0x4FB448F4, 0x5229D06D, // 8
+            0x576B8599, 0x7423407F, // 9
+            0x5E4786DA, 0x3210BAF7, // 10
+            0x644B2C92, 0x431B3947, // 11
+            0x697E90CE, 0x77C362C4, // 12
+            0x6DEE0B96, 0x2798C9CE, // 13
+            0x71A92144, 0x5765FCE4, // 14
+            0x74C16FD5, 0x1E2A0990, // 15
+            0x7749AC92, 0x0DF36EEB, // 16
+            0x7954BFA4, 0x28079289, // 17
+            0x7AF5067A, 0x2EDC2050, // 18
+            0x7C3BC17C, 0x123D5E7B, // 19
+            0x7D38AD76, 0x2A9381D9, // 20
+            0x7DF9C5DF, 0x0E868CA7, // 21
+            0x7E8B2ABA, 0x18E5C811, // 22
+            0x7EF7237C, 0x00908272, // 23
+            0x7F4637C5, 0x6DBA5126, // 24
+            0x7F7F5707, 0x4A52EDEB, // 25
+            0x7FA808CC, 0x23290599, // 26
+            0x7FC4A083, 0x69BDF2D5, // 27
+            0x7FD870CA, 0x42275558, // 28
+            0x7FE5FB5D, 0x3EF82C1B, // 29
+            0x7FEF1BFA, 0x6C03A362, // 30
+            0x7FF52D4E, 0x316C2C8C, // 31
+            0x7FF927BA, 0x12AE54AF, // 32
+            0x7FFBBA43, 0x749CC0E2, // 33
+            0x7FFD5E3D, 0x4524AD91, // 34
+            0x7FFE6664, 0x535785B5, // 35
+            0x7FFF0A41, 0x0B291681, // 36
+            0x7FFF6E81, 0x132C3D6F, // 37
+            0x7FFFAAFE, 0x4DBC6BED, // 38
+            0x7FFFCEFD, 0x7A1E2D14, // 39
+            0x7FFFE41E, 0x4C6EC115, // 40
+            0x7FFFF059, 0x319503C8, // 41
+            0x7FFFF754, 0x5DDD0D40, // 42
+            0x7FFFFB43, 0x0B9E9823, // 43
+            0x7FFFFD71, 0x76B81AE1, // 44
+            0x7FFFFEA3, 0x7E66A1EC, // 45
+            0x7FFFFF49, 0x26F6E191, // 46
+            0x7FFFFFA1, 0x2FA31694, // 47
+            0x7FFFFFCF, 0x5247BEC9, // 48
+            0x7FFFFFE7, 0x4F4127C7, // 49
+            0x7FFFFFF3, 0x6FAA69FD, // 50
+            0x7FFFFFFA, 0x0630D073, // 51
+            0x7FFFFFFD, 0x0F2957BB, // 52
+            0x7FFFFFFE, 0x4FD29432, // 53
+            0x7FFFFFFF, 0x2CFAD60D, // 54
+            0x7FFFFFFF, 0x5967A930, // 55
+            0x7FFFFFFF, 0x6E4C9DFF, // 56
+            0x7FFFFFFF, 0x77FDCCC8, // 57
+            0x7FFFFFFF, 0x7C6CE89E, // 58
+            0x7FFFFFFF, 0x7E6D116F, // 59
+            0x7FFFFFFF, 0x7F50FA31, // 60
+            0x7FFFFFFF, 0x7FB50089, // 61
+            0x7FFFFFFF, 0x7FE04C2D, // 62
+            0x7FFFFFFF, 0x7FF2C7C1, // 63
+            0x7FFFFFFF, 0x7FFA8FE3, // 64
+            0x7FFFFFFF, 0x7FFDCB1B, // 65
+            0x7FFFFFFF, 0x7FFF1DE2, // 66
+            0x7FFFFFFF, 0x7FFFA6B7, // 67
+            0x7FFFFFFF, 0x7FFFDD39, // 68
+            0x7FFFFFFF, 0x7FFFF2A3, // 69
+            0x7FFFFFFF, 0x7FFFFAEF, // 70
+            0x7FFFFFFF, 0x7FFFFE1B, // 71
+            0x7FFFFFFF, 0x7FFFFF4D, // 72
+            0x7FFFFFFF, 0x7FFFFFBF, // 73
+            0x7FFFFFFF, 0x7FFFFFE9, // 74
+            0x7FFFFFFF, 0x7FFFFFF8, // 75
+            0x7FFFFFFF, 0x7FFFFFFD, // 76
+            0x7FFFFFFF, 0x7FFFFFFF, // 77
         };
 
-
-
-
-
-        static void sample_gauss_polly(int nonce, byte[] seed, int seedOffset, long[] poly, int polyOffset)
+        static void sample_gauss_poly(int nonce, byte[] seed, int seedOffset, int[] poly, int polyOffset)
         {
             int dmsp = nonce << 8;
 
@@ -873,7 +812,6 @@
 
             for (int chunk = 0; chunk < PARAM_N; chunk += CHUNK_SIZE)
             {
-
                 HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                     samp, 0, CHUNK_SIZE * CDT_COLS * 4, (short)dmsp++, seed, seedOffset, CRYPTO_SEEDBYTES);
 
@@ -882,27 +820,24 @@
                     for (int j = 1; j < CDT_ROWS; j++) {
                         borrow = 0;
                         for (int k = CDT_COLS-1; k >= 0; k--) {
-                            c[k] = (int)(( at(samp, 0,i*CDT_COLS+k) & mask) - (cdt_v[j*CDT_COLS+k] + borrow));
+                            c[k] = (at(samp, i*CDT_COLS, k) & mask) - (cdt_v[j*CDT_COLS+k] + borrow);
                             borrow = c[k] >> (RADIX32-1);
                         }
                         poly[polyOffset+chunk+i] += ~borrow & 1;
                     }
-                    sign =  at(samp,0,i*CDT_COLS) >> (RADIX32-1);
+
+//                    sign =  at(samp,i*CDT_COLS, 0) >> (RADIX32-1);
+                    sign = (int)samp[((i*CDT_COLS) << 2) + 3] >> (RADIX32 - 1);
+
                     poly[polyOffset+chunk+i] = (sign & -poly[polyOffset+chunk+i]) | (~sign & poly[polyOffset+chunk+i]);
                 }
-
             }
-
         }
-
     }
 
-
     static class QTesla1PPolynomial
     {
-
-
-        private static final long[] zeta = new long[]{
+        private static final int[] zeta = new int[]{
             184007114, 341297933, 172127038, 306069179, 260374244, 269720605, 20436325, 2157599, 36206659, 61987110, 112759694, 92762708, 278504038, 139026960, 183642748, 298230187,
             37043356, 230730845, 107820937, 97015745, 156688276, 38891102, 170244636, 259345227, 170077366, 141586883, 100118513, 328793523, 289946488, 263574185, 132014089, 14516260,
             87424978, 192691578, 190961717, 262687761, 333967048, 12957952, 326574509, 273585413, 151922543, 195893203, 261889302, 120488377, 169571794, 44896463, 128576039, 68257019,
@@ -969,7 +904,7 @@
             254049694, 285174861, 264316834, 11792643, 149333889, 214699018, 261331547, 317320791, 24527858, 118790777, 264146824, 174296812, 332779737, 94199786, 288227027, 172048372,
         };
 
-        private static final long[] zetainv = new long[]{
+        private static final int[] zetainv = new int[]{
             55349550, 249376791, 10796840, 169279765, 79429753, 224785800, 319048719, 26255786, 82245030, 128877559, 194242688, 331783934, 79259743, 58401716, 89526883, 107622248,
             126812171, 206603058, 33048689, 37579319, 62444874, 9574084, 8041001, 174424626, 78818320, 129371885, 166295850, 139513654, 199147441, 68038492, 277843711, 65999573,
             21850993, 252252426, 124803757, 15185295, 68854578, 54386191, 197879894, 131754200, 265727759, 156946887, 166260901, 255298661, 209284049, 222086502, 264918555, 105866478,
@@ -1036,8 +971,7 @@
             159933829, 204549617, 65072539, 250813869, 230816883, 281589467, 307369918, 341418978, 323140252, 73855972, 83202333, 37507398, 171449539, 2278644, 159569463, 171528205,
         };
 
-
-        static void poly_uniform(long[] a, byte[] seed, int seedOffset)
+        static void poly_uniform(int[] a, byte[] seed, int seedOffset)
         {
             int pos = 0, i = 0, nbytes = (PARAM_Q_LOG + 7) / 8;
             int nblocks = PARAM_GEN_A;
@@ -1045,20 +979,20 @@
             byte[] buf = new byte[HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A];
             short dmsp = 0;
 
-
+            // GenA: the XOF is instantiated with cSHAKE128 (see Algorithm 10).
             HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                 buf, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A,
                 dmsp++,
                 seed, seedOffset, CRYPTO_RANDOMBYTES
             );
 
-
             while (i < PARAM_K * PARAM_N)
             {
                 if (pos > HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * nblocks - 4 * nbytes)
                 {
                     nblocks = 1;
 
+                    // GenA: the XOF is instantiated with cSHAKE128 (see Algorithm 10).
                     HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                         buf, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A,
                         dmsp++,
@@ -1067,6 +1001,7 @@
 
                     pos = 0;
                 }
+
                 val1 = Pack.littleEndianToInt(buf, pos) & mask;
                 pos += nbytes;
                 val2 = Pack.littleEndianToInt(buf, pos) & mask;
@@ -1075,6 +1010,7 @@
                 pos += nbytes;
                 val4 = Pack.littleEndianToInt(buf, pos) & mask;
                 pos += nbytes;
+
                 if (val1 < PARAM_Q && i < PARAM_K * PARAM_N)
                 {
                     a[i++] = reduce((long)val1 * PARAM_R2_INVN);
@@ -1094,19 +1030,17 @@
             }
         }
 
-
-        static long reduce(long a)
+        static int reduce(long a)
         { // Montgomery reduction
             long u;
 
             u = (a * (long)PARAM_QINV) & 0xFFFFFFFFL;
             u *= PARAM_Q;
             a += u;
-            return a >> 32;
+            return (int)(a >> 32);
         }
 
-
-        static void ntt(long[] a, long[] w)
+        static void ntt(int[] a, int[] w)
         { // Forward NTT transform
             int NumoProblems = PARAM_N >> 1, jTwiddle = 0;
 
@@ -1115,58 +1049,36 @@
                 int jFirst, j = 0;
                 for (jFirst = 0; jFirst < PARAM_N; jFirst = j + NumoProblems)
                 {
-                    long W = (int)w[jTwiddle++];
+                    int W = w[jTwiddle++];
                     for (j = jFirst; j < jFirst + NumoProblems; j++)
                     {
-                        long temp = reduce(W * a[j + NumoProblems]);
-                        a[j + NumoProblems] = a[j] + (PARAM_Q - temp);
-                        a[j] = temp + a[j];
+                        int a_j = a[j], a_n = a[j + NumoProblems];
+                        int temp = reduce((long)W * a_n);
+                        a[j] = correct(a_j + temp - PARAM_Q);
+                        a[j + NumoProblems] = correct(a_j - temp);
                     }
                 }
             }
         }
 
-
-        static long barr_reduce(long a)
+        private static int barr_reduce(int a)
         { // Barrett reduction
-            long u = (((long)a * PARAM_BARR_MULT) >> PARAM_BARR_DIV); // TODO u may need to be cast back to int.
+            int u = (int)(((long)a * PARAM_BARR_MULT) >> PARAM_BARR_DIV);
             return a - u * PARAM_Q;
         }
 
-
-        static void nttinv(long[] a, long[] w)
-        { // Inverse NTT transform
-            int NumoProblems = 1, jTwiddle = 0;
-            for (NumoProblems = 1; NumoProblems < PARAM_N; NumoProblems *= 2)
-            {
-                int jFirst, j = 0;
-                for (jFirst = 0; jFirst < PARAM_N; jFirst = j + NumoProblems)
-                {
-                    int W = (int)w[jTwiddle++];
-                    for (j = jFirst; j < jFirst + NumoProblems; j++)
-                    {
-                        long temp = a[j];
-
-                        if (NumoProblems == 16)
-                        {
-                            a[j] = barr_reduce(temp + a[j + NumoProblems]);
-                        }
-                        else
-                        {
-                            a[j] = temp + a[j + NumoProblems];
-                        }
-                        a[j + NumoProblems] = reduce((long)W * (temp - a[j + NumoProblems]));
-                    }
-                }
-            }
-
-            for (int i = 0; i < PARAM_N / 2; i++)
-            {
-                a[i] = reduce((long)PARAM_R * a[i]);
-            }
+        private static int barr_reduce64(long a)
+        { // Barrett reduction
+            long u = (a * PARAM_BARR_MULT) >> PARAM_BARR_DIV;
+            return (int)(a - u * PARAM_Q);
         }
 
-        static void nttinv(long[] a, int aPos, long[] w)
+        private static int correct(int x)
+        {
+            return x + ((x >> (RADIX32 - 1)) & PARAM_Q);
+        }
+
+        static void nttinv(int[] a, int aPos, int[] w)
         { // Inverse NTT transform
             int NumoProblems = 1, jTwiddle = 0;
             for (NumoProblems = 1; NumoProblems < PARAM_N; NumoProblems *= 2)
@@ -1174,32 +1086,18 @@
                 int jFirst, j = 0;
                 for (jFirst = 0; jFirst < PARAM_N; jFirst = j + NumoProblems)
                 {
-                    int W = (int)w[jTwiddle++];
+                    int W = w[jTwiddle++];
                     for (j = jFirst; j < jFirst + NumoProblems; j++)
                     {
-                        long temp = a[aPos + j];
-                        a[aPos + j] = temp + a[aPos + j + NumoProblems];
-                        a[aPos + j + NumoProblems] = reduce((long)W * (temp + (2 * PARAM_Q - a[aPos + j + NumoProblems])));
-                    }
-                }
-
-
-                NumoProblems *= 2;
-                for (jFirst = 0; jFirst < PARAM_N; jFirst = j + NumoProblems)
-                {
-                    int W = (int)w[jTwiddle++];
-                    for (j = jFirst; j < jFirst + NumoProblems; j++)
-                    {
-                        long temp = a[aPos + j];
+                        int temp = a[aPos + j];
                         a[aPos + j] = barr_reduce(temp + a[aPos + j + NumoProblems]);
-                        a[aPos + j + NumoProblems] = reduce((long)W * (temp + (2 * PARAM_Q - a[aPos + j + NumoProblems])));
+                        a[aPos + j + NumoProblems] = reduce((long)W * (temp - a[aPos + j + NumoProblems]));
                     }
                 }
             }
         }
 
-
-        static void poly_ntt(long[] x_ntt, long[] x)
+        static void poly_ntt(int[] x_ntt, int[] x)
         { // Call to NTT function. Avoids input destruction
 
             for (int i = 0; i < PARAM_N; i++)
@@ -1209,17 +1107,7 @@
             ntt(x_ntt, zeta);
         }
 
-
-        static void poly_pointwise(long[] result, long[] x, long[] y)
-        { // Pointwise polynomial multiplication result = x.y
-
-            for (int i = 0; i < PARAM_N; i++)
-            {
-                result[i] = reduce((long)x[i] * y[i]);
-            }
-        }
-
-        static void poly_pointwise(long[] result, int rpos, long[] x, int xpos, long[] y)
+        static void poly_pointwise(int[] result, int rpos, int[] x, int xpos, int[] y)
         { // Pointwise polynomial multiplication result = x.y
 
             for (int i = 0; i < PARAM_N; i++)
@@ -1228,25 +1116,14 @@
             }
         }
 
-
-        static void poly_mul(long[] result, long[] x, long[] y)
-        { // Polynomial multiplication result = x*y, with in place reduction for (X^N+1)
-            // The input x is assumed to be in NTT form
-
-            poly_pointwise(result, x, y);
-            nttinv(result, zetainv);
-        }
-
-
-        static void poly_mul(long[] result, int rpos, long[] x, int xpos, long[] y)
+        static void poly_mul(int[] result, int rpos, int[] x, int xpos, int[] y)
         { // Polynomial multiplication result = x*y, with in place reduction for (X^N+1)
 
             poly_pointwise(result, rpos, x, xpos, y);
             nttinv(result, rpos, zetainv);
         }
 
-
-        static void poly_add(long[] result, long[] x, long[] y)
+        static void poly_add(int[] result, int[] x, int[] y)
         { // Polynomial addition result = x+y
 
             for (int i = 0; i < PARAM_N; i++)
@@ -1255,7 +1132,26 @@
             }
         }
 
-        static void poly_sub(long[] result, int rpos, long[] x, int xpos, long[] y, int ypos)
+        static void poly_add_correct(int[] result, int rpos, int[] x, int xpos, int[] y, int ypos)
+        { // Polynomial addition result = x+y with correction
+
+            for (int i = 0; i < PARAM_N; i++)
+            {
+                int ri = correct(x[xpos + i] + y[ypos + i]);
+                result[rpos + i] = correct(ri - PARAM_Q);
+            }
+        }
+
+        static void poly_sub(int[] result, int rpos, int[] x, int xpos, int[] y, int ypos)
+        { // Polynomial subtraction result = x-y
+
+            for (int i = 0; i < PARAM_N; i++)
+            {
+                result[rpos + i] = x[xpos + i] - y[ypos + i];
+            }
+        }
+
+        static void poly_sub_reduce(int[] result, int rpos, int[] x, int xpos, int[] y, int ypos)
         { // Polynomial subtraction result = x-y
 
             for (int i = 0; i < PARAM_N; i++)
@@ -1264,32 +1160,9 @@
             }
         }
 
-
-        static void poly_add_correct(long[] result, int rpos, long[] x, int xpos, long[] y, int ypos)
-        { // Polynomial addition result = x+y with correction
-
-            for (int i = 0; i < PARAM_N; i++)
-            {
-                result[rpos + i] = x[xpos + i] + y[ypos + i];
-                result[rpos + i] -= PARAM_Q;
-                result[rpos + i] += (result[rpos + i] >> (RADIX32 - 1)) & PARAM_Q;   // If result[i] >= q then subtract q
-            }
-        }
-
-
-        static void poly_sub_correct(int[] result, int[] x, int[] y)
-        { // Polynomial subtraction result = x-y with correction
-
-            for (int i = 0; i < PARAM_N; i++)
-            {
-                result[i] = x[i] - y[i];
-                result[i] += (result[i] >> (RADIX32 - 1)) & PARAM_Q;    // If result[i] < 0 then add q
-            }
-        }
-
-
-        static void sparse_mul8(long[] prod, int ppos, byte[] s, int spos, int[] pos_list, short[] sign_list)
+        static void sparse_mul8(int[] prod, int ppos, byte[] s, int spos, int[] pos_list, short[] sign_list)
         {
+            // TODO Review multiplications involving elements of s (an unsigned char* in reference implementation)
             int i, j, pos;
 
             for (i = 0; i < PARAM_N; i++)
@@ -1311,104 +1184,28 @@
             }
         }
 
-
-        static void sparse_mul8(long[] prod, byte[] s, int[] pos_list, short[] sign_list)
+        static void sparse_mul32(int[] prod, int ppos, int[] pk, int pkPos, int[] pos_list, short[] sign_list)
         {
             int i, j, pos;
-            byte t[] = s;
-
-            for (i = 0; i < PARAM_N; i++)
-            {
-                prod[i] = 0;
-            }
+            long[] temp = new long[PARAM_N];
 
             for (i = 0; i < PARAM_H; i++)
             {
                 pos = pos_list[i];
                 for (j = 0; j < pos; j++)
                 {
-                    prod[j] = prod[j] - sign_list[i] * t[j + PARAM_N - pos];
+                    temp[j] = temp[j] - sign_list[i] * pk[pkPos + j + PARAM_N - pos];
                 }
                 for (j = pos; j < PARAM_N; j++)
                 {
-                    prod[j] = prod[j] + sign_list[i] * t[j - pos];
+                    temp[j] = temp[j] + sign_list[i] * pk[pkPos + j - pos];
                 }
             }
-        }
-
-
-        static void sparse_mul16(int[] prod, int s[], int pos_list[], short sign_list[])
-        {
-            int i, j, pos;
-//            short[] t = s;
 
             for (i = 0; i < PARAM_N; i++)
             {
-                prod[i] = 0;
-            }
-
-            for (i = 0; i < PARAM_H; i++)
-            {
-                pos = pos_list[i];
-                for (j = 0; j < pos; j++)
-                {
-                    prod[j] = prod[j] - sign_list[i] * s[j + PARAM_N - pos];
-                }
-                for (j = pos; j < PARAM_N; j++)
-                {
-                    prod[j] = prod[j] + sign_list[i] * s[j - pos];
-                }
+                prod[ppos + i] = barr_reduce64(temp[i]);
             }
         }
-
-
-        static void sparse_mul32(int[] prod, int[] pk, int[] pos_list, short[] sign_list)
-        {
-            int i, j, pos;
-
-            for (i = 0; i < PARAM_N; i++)
-            {
-                prod[i] = 0;
-            }
-
-            for (i = 0; i < PARAM_H; i++)
-            {
-                pos = pos_list[i];
-                for (j = 0; j < pos; j++)
-                {
-                    prod[j] = prod[j] - sign_list[i] * pk[j + PARAM_N - pos];
-                }
-                for (j = pos; j < PARAM_N; j++)
-                {
-                    prod[j] = prod[j] + sign_list[i] * pk[j - pos];
-                }
-            }
-        }
-
-        static void sparse_mul32(long[] prod, int ppos, int[] pk, int pkPos, int[] pos_list, short[] sign_list)
-        {
-            int i, j, pos;
-
-            for (i = 0; i < PARAM_N; i++)
-            {
-                prod[ppos + i] = 0;
-            }
-
-            for (i = 0; i < PARAM_H; i++)
-            {
-                pos = pos_list[i];
-                for (j = 0; j < pos; j++)
-                {
-                    prod[ppos + j] = prod[ppos + j] - sign_list[i] * pk[pkPos + j + PARAM_N - pos];
-                }
-                for (j = pos; j < PARAM_N; j++)
-                {
-                    prod[ppos + j] = prod[ppos + j] + sign_list[i] * pk[pkPos + j - pos];
-                }
-            }
-        }
-
-
     }
-
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla3p.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla3p.java
index eed4d1d..7a8ce3c 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla3p.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/qtesla/QTesla3p.java
@@ -7,9 +7,8 @@
 
 class QTesla3p
 {
-
     private static final int PARAM_N = 2048;
-    private static final double PARAM_SIGMA = 8.5;
+//    private static final double PARAM_SIGMA = 8.5;
     private static final int PARAM_Q = 856145921;
     private static final int PARAM_Q_LOG = 30;
     private static final long PARAM_QINV = 587710463;
@@ -17,9 +16,9 @@
     private static final int PARAM_BARR_DIV = 32;
     private static final int PARAM_B = 2097151;
     private static final int PARAM_B_BITS = 21;
-    private static final int PARAM_S_BITS = 8;
+//    private static final int PARAM_S_BITS = 8;
     private static final int PARAM_K = 5;
-    private static final double PARAM_SIGMA_E = PARAM_SIGMA;
+//    private static final double PARAM_SIGMA_E = PARAM_SIGMA;
     private static final int PARAM_H = 40;
     private static final int PARAM_D = 24;
     private static final int PARAM_GEN_A = 180;
@@ -28,31 +27,26 @@
     private static final int PARAM_KEYGEN_BOUND_S = 901;
     private static final int PARAM_S = PARAM_KEYGEN_BOUND_S;
     private static final int PARAM_R2_INVN = 513161157;
-    private static final int PARAM_R = 14237691;
-
+//    private static final int PARAM_R = 14237691;
 
     private static final int CRYPTO_RANDOMBYTES = 32;
     private static final int CRYPTO_SEEDBYTES = 32;
     private static final int CRYPTO_C_BYTES = 32;
-    private static final int HM_BYTES = 64;
+    private static final int HM_BYTES = 40;
 
-    private static final int RADIX = 32;
+//    private static final int RADIX = 32;
     private static final int RADIX32 = 32;
 
 
     static final int CRYPTO_BYTES = ((PARAM_N * (PARAM_B_BITS + 1) + 7) / 8 + CRYPTO_C_BYTES);
     // Contains polynomial s and e, and seeds seed_a and seed_y
-    static final int CRYPTO_SECRETKEYBYTES = (1 * PARAM_N + 1 * PARAM_N * PARAM_K + 2 * CRYPTO_SEEDBYTES);
+    static final int CRYPTO_SECRETKEYBYTES = (1 * PARAM_N + 1 * PARAM_N * PARAM_K + 2 * CRYPTO_SEEDBYTES + HM_BYTES);
 
     // Contains seed_a and polynomials t
     static final int CRYPTO_PUBLICKEYBYTES = ((PARAM_Q_LOG * PARAM_N * PARAM_K + 7) / 8 + CRYPTO_SEEDBYTES);
 
-
-    static int generateKeyPair(
-
-        byte[] publicKey, byte[] privateKey, SecureRandom secureRandom)
+    static int generateKeyPair(byte[] publicKey, byte[] privateKey, SecureRandom secureRandom)
     {
-
         /* Initialize Domain Separator for Error Polynomial and Secret Polynomial */
         int nonce = 0;
 
@@ -69,16 +63,11 @@
         long[] s_ntt = new long[PARAM_N];
 
         /* Get randomnessExtended <- seedErrorPolynomial, seedSecretPolynomial, seedA, seedY */
-        // this.rng.randomByte (randomness, (short) 0, Polynomial.RANDOM);
         secureRandom.nextBytes(randomness);
 
-
-        HashUtils.secureHashAlgorithmKECCAK256(randomnessExtended, 0, (PARAM_K + 3) * CRYPTO_SEEDBYTES, randomness, 0, CRYPTO_RANDOMBYTES);
-
-
-
-
-
+        HashUtils.secureHashAlgorithmKECCAK256(
+            randomnessExtended, 0, (PARAM_K + 3) * CRYPTO_SEEDBYTES,
+            randomness, 0, CRYPTO_RANDOMBYTES);
 
         /*
          * Sample the Error Polynomial Fulfilling the Criteria
@@ -95,7 +84,6 @@
             while (checkPolynomial(errorPolynomial, k * PARAM_N, PARAM_KEYGEN_BOUND_E));
         }
 
-
         /*
          * Sample the Secret Polynomial Fulfilling the Criteria
          * Choose Secret Polynomial in R with Entries from D_SIGMA
@@ -103,35 +91,26 @@
          */
         do
         {
-
             Gaussian.sample_gauss_poly(++nonce, randomnessExtended, PARAM_K * CRYPTO_SEEDBYTES, secretPolynomial, 0);
-
-            //Sample.polynomialGaussSamplerI(secretPolynomial, 0, randomnessExtended, Polynomial.SEED, ++nonce);
         }
         while (checkPolynomial(secretPolynomial, 0, PARAM_KEYGEN_BOUND_S));
 
-
         QTesla3PPolynomial.poly_uniform(A, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
         QTesla3PPolynomial.poly_ntt(s_ntt, secretPolynomial);
 
-
         for (int k = 0; k < PARAM_K; k++)
         {
             QTesla3PPolynomial.poly_mul(T, k * PARAM_N, A, k * PARAM_N, s_ntt);
             QTesla3PPolynomial.poly_add_correct(T, k * PARAM_N, T, k * PARAM_N, errorPolynomial, k * PARAM_N);
         }
 
-
         /* Pack Public and Private Keys */
-
-        encodePrivateKey(privateKey, secretPolynomial, errorPolynomial, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
         encodePublicKey(publicKey, T, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES);
+        encodePrivateKey(privateKey, secretPolynomial, errorPolynomial, randomnessExtended, (PARAM_K + 1) * CRYPTO_SEEDBYTES, publicKey);
 
         return 0;
-
     }
 
-
     static int generateSignature(
         byte[] signature,
         final byte[] message, int messageOffset, int messageLength,
@@ -139,7 +118,7 @@
     {
         byte[] c = new byte[CRYPTO_C_BYTES];
         byte[] randomness = new byte[CRYPTO_SEEDBYTES];
-        byte[] randomness_input = new byte[CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES];
+        byte[] randomness_input = new byte[CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES + 2 * HM_BYTES];
         int[] pos_list = new int[PARAM_H];
         short[] sign_list = new short[PARAM_H];
         long[] y = new long[PARAM_N];
@@ -156,25 +135,25 @@
         int nonce = 0;  // Initialize domain separator for sampling y
         boolean rsp = false;
 
-        //  randombytes(randomness_input + CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
-        byte[] temporaryRandomnessInput = new byte[CRYPTO_RANDOMBYTES];
-        secureRandom.nextBytes(temporaryRandomnessInput);
-        System.arraycopy(temporaryRandomnessInput, 0, randomness_input, CRYPTO_RANDOMBYTES, CRYPTO_RANDOMBYTES);
-        // --
+        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES - CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_SEEDBYTES);
 
-
-        //  memcpy(randomness_input, &sk[CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES], CRYPTO_SEEDBYTES);
-        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_SEEDBYTES);
-        // --
+        {
+            byte[] tmp = new byte[CRYPTO_RANDOMBYTES];
+            secureRandom.nextBytes(tmp);
+            System.arraycopy(tmp, 0, randomness_input, CRYPTO_SEEDBYTES, CRYPTO_RANDOMBYTES);
+        }
 
         HashUtils.secureHashAlgorithmKECCAK256(
-            randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES, HM_BYTES, message, 0, messageLength);
+            randomness_input, CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES, HM_BYTES,
+            message, 0, messageLength);
 
         HashUtils.secureHashAlgorithmKECCAK256(
-            randomness, 0, CRYPTO_SEEDBYTES, randomness_input, 0, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES + HM_BYTES);
+            randomness, 0, CRYPTO_SEEDBYTES,
+            randomness_input, 0, randomness_input.length - HM_BYTES);
 
+        System.arraycopy(privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES, randomness_input, randomness_input.length - HM_BYTES, HM_BYTES);
 
-        QTesla3PPolynomial.poly_uniform(a, privateKey, CRYPTO_SECRETKEYBYTES - 2 * CRYPTO_SEEDBYTES);
+        QTesla3PPolynomial.poly_uniform(a, privateKey, CRYPTO_SECRETKEYBYTES - HM_BYTES - 2 * CRYPTO_SEEDBYTES);
 
         while (true)
         {
@@ -186,7 +165,7 @@
                 QTesla3PPolynomial.poly_mul(v, k * PARAM_N, a, k * PARAM_N, y_ntt);
             }
 
-            hashFunction(c, 0, v, randomness_input, CRYPTO_RANDOMBYTES + CRYPTO_SEEDBYTES);
+            hashFunction(c, 0, v, randomness_input, CRYPTO_SEEDBYTES + CRYPTO_RANDOMBYTES);
             encodeC(pos_list, sign_list, c, 0);
 
             QTesla3PPolynomial.sparse_mul8(Sc, privateKey, pos_list, sign_list);
@@ -213,29 +192,25 @@
                 continue;
             }
 
-
             encodeSignature(signature, 0, c, 0, z);
             return 0;
-
         }
 
         // return 0;
     }
 
-
     static int verifying(
         byte[] message,
         final byte[] signature, int signatureOffset, int signatureLength,
         final byte[] publicKey)
     {
-
-        byte c[] = new byte[CRYPTO_C_BYTES];
-        byte c_sig[] = new byte[CRYPTO_C_BYTES];
-        byte seed[] = new byte[CRYPTO_SEEDBYTES];
-        byte hm[] = new byte[HM_BYTES];
-        int pos_list[] = new int[PARAM_H];
-        short sign_list[] = new short[PARAM_H];
-        int pk_t[] = new int[PARAM_N * PARAM_K];
+        byte[] c = new byte[CRYPTO_C_BYTES];
+        byte[] c_sig = new byte[CRYPTO_C_BYTES];
+        byte[] seed = new byte[CRYPTO_SEEDBYTES];
+        byte[] hm = new byte[2 * HM_BYTES];
+        int[] pos_list = new int[PARAM_H];
+        short[] sign_list = new short[PARAM_H];
+        int[] pk_t = new int[PARAM_N * PARAM_K];
         long[] w = new long[PARAM_N * PARAM_K];
         long[] a = new long[PARAM_N * PARAM_K];
         long[] Tc = new long[PARAM_N * PARAM_K];
@@ -245,20 +220,26 @@
 
         int k = 0;
 
-        if (signatureLength < CRYPTO_BYTES)
+        if (signatureLength != CRYPTO_BYTES)
         {
             return -1;
         }
 
         decodeSignature(c, z, signature, signatureOffset);
-
         if (testZ(z))
         {
             return -2;
         }
-
-
         decodePublicKey(pk_t, seed, 0, publicKey);
+
+        // Get H(m) and hash_pk^M
+        HashUtils.secureHashAlgorithmKECCAK256(
+            hm, 0, HM_BYTES,
+            message, 0, message.length);
+        HashUtils.secureHashAlgorithmKECCAK256(
+            hm, HM_BYTES, HM_BYTES,
+            publicKey, 0, CRYPTO_PUBLICKEYBYTES - CRYPTO_SEEDBYTES);
+
         QTesla3PPolynomial.poly_uniform(a, seed, 0);
         encodeC(pos_list, sign_list, c, 0);
         QTesla3PPolynomial.poly_ntt(z_ntt, z);
@@ -270,9 +251,6 @@
             QTesla3PPolynomial.poly_sub(w, k * PARAM_N, w, k * PARAM_N, Tc, k * PARAM_N);
         }
 
-        HashUtils.secureHashAlgorithmKECCAK256(
-            hm, 0, HM_BYTES, message, 0, message.length
-        );
         hashFunction(c_sig, 0, w, hm, 0);
 
         if (!memoryEqual(c, 0, c_sig, 0, CRYPTO_C_BYTES))
@@ -283,10 +261,9 @@
         return 0;
     }
 
-
-    static void encodePrivateKey(byte[] privateKey, final long[] secretPolynomial, final long[] errorPolynomial, final byte[] seed, int seedOffset)
+    static void encodePrivateKey(byte[] privateKey, final long[] secretPolynomial, final long[] errorPolynomial,
+        final byte[] seed, int seedOffset, byte[] publicKey)
     {
-
         int i, k = 0;
         int skPtr = 0;
 
@@ -294,28 +271,33 @@
         {
             privateKey[skPtr + i] = (byte)secretPolynomial[i];
         }
-
         skPtr += PARAM_N;
+
         for (k = 0; k < PARAM_K; k++)
         {
             for (i = 0; i < PARAM_N; i++)
             {
                 privateKey[skPtr + (k * PARAM_N + i)] = (byte)errorPolynomial[k * PARAM_N + i];
-                //  System.out.printf("%d,   %x\n", skPtr + (k * PARAM_N + i), privateKey[skPtr + (k * PARAM_N + i)]);
             }
         }
+        skPtr += PARAM_K * PARAM_N;
 
-        System.arraycopy(seed, seedOffset, privateKey, skPtr + (PARAM_K * PARAM_N), CRYPTO_SEEDBYTES * 2);
+        System.arraycopy(seed, seedOffset, privateKey, skPtr, CRYPTO_SEEDBYTES * 2);
+        skPtr += CRYPTO_SEEDBYTES * 2;
 
+        /* Hash of the public key */
+        HashUtils.secureHashAlgorithmKECCAK256(
+            privateKey, skPtr, HM_BYTES,
+            publicKey, 0, CRYPTO_PUBLICKEYBYTES - CRYPTO_SEEDBYTES);
+        skPtr += HM_BYTES;
+
+//        assert CRYPTO_SECRETKEYBYTES == skPtr;
     }
 
-
     static void encodePublicKey(byte[] publicKey, final long[] T, final byte[] seedA, int seedAOffset)
     {
-
         int j = 0;
 
-
         for (int i = 0; i < (PARAM_N * PARAM_K * PARAM_Q_LOG / 32); i += 15)
         {
             at(publicKey, i, 0, (int)(T[j] | (T[j + 1] << 30)));
@@ -395,7 +377,6 @@
 
     }
 
-
     private static final int maskb1 = ((1 << (PARAM_B_BITS + 1)) - 1);
 
     static void encodeSignature(byte[] signature, int signatureOffset, byte[] C, int cOffset, long[] Z)
@@ -419,51 +400,55 @@
         }
 
         System.arraycopy(C, cOffset, signature, signatureOffset + PARAM_N * (PARAM_B_BITS + 1) / 8, CRYPTO_C_BYTES);
-
     }
 
-
     static void decodeSignature(byte[] C, long[] Z, final byte[] signature, int signatureOffset)
     {
-
         int j = 0;
         for (int i = 0; i < PARAM_N; i += 16)
         {
-            Z[i] = (at(signature, j, 0) << 10) >> 10;
-            Z[i + 1] = (at(signature, j, 0) >>> 22) | ((at(signature, j, 1) << 20) >> 10);
-            Z[i + 2] = (at(signature, j, 1) >>> 12) | ((at(signature, j, 2) << 30) >> 10);
-            Z[i + 3] = ((at(signature, j, 2) << 8) >> 10);
-            Z[i + 4] = (at(signature, j, 2) >>> 24) | ((at(signature, j, 3) << 18) >> 10);
-            Z[i + 5] = (at(signature, j, 3) >>> 14) | ((at(signature, j, 4) << 28) >> 10);
-            Z[i + 6] = ((at(signature, j, 4) << 6) >> 10);
-            Z[i + 7] = (at(signature, j, 4) >>> 26) | ((at(signature, j, 5) << 16) >> 10);
-            Z[i + 8] = (at(signature, j, 5) >>> 16) | ((at(signature, j, 6) << 26) >> 10);
-            Z[i + 9] = ((at(signature, j, 6) << 4) >> 10);
-            Z[i + 10] = (at(signature, j, 6) >>> 28) | ((at(signature, j, 7) << 14) >> 10);
-            Z[i + 11] = (at(signature, j, 7) >>> 18) | ((at(signature, j, 8) << 24) >> 10);
-            Z[i + 12] = ((at(signature, j, 8) << 2) >> 10);
-            Z[i + 13] = (at(signature, j, 8) >>> 30) | ((at(signature, j, 9) << 12) >> 10);
-            Z[i + 14] = (at(signature, j, 9) >>> 20) | ((at(signature, j, 10) << 22) >> 10);
-            Z[i + 15] = (at(signature, j, 10) >> 10);
+            int s0 = at(signature, j, 0);
+            int s1 = at(signature, j, 1);
+            int s2 = at(signature, j, 2);
+            int s3 = at(signature, j, 3);
+            int s4 = at(signature, j, 4);
+            int s5 = at(signature, j, 5);
+            int s6 = at(signature, j, 6);
+            int s7 = at(signature, j, 7);
+            int s8 = at(signature, j, 8);
+            int s9 = at(signature, j, 9);
+            int s10 = at(signature, j, 10);
 
+            Z[i] = (s0 << 10) >> 10;
+            Z[i + 1] = (s0 >>> 22) | ((s1 << 20) >> 10);
+            Z[i + 2] = (s1 >>> 12) | ((s2 << 30) >> 10);
+            Z[i + 3] = ((s2 << 8) >> 10);
+            Z[i + 4] = (s2 >>> 24) | ((s3 << 18) >> 10);
+            Z[i + 5] = (s3 >>> 14) | ((s4 << 28) >> 10);
+            Z[i + 6] = ((s4 << 6) >> 10);
+            Z[i + 7] = (s4 >>> 26) | ((s5 << 16) >> 10);
+            Z[i + 8] = (s5 >>> 16) | ((s6 << 26) >> 10);
+            Z[i + 9] = ((s6 << 4) >> 10);
+            Z[i + 10] = (s6 >>> 28) | ((s7 << 14) >> 10);
+            Z[i + 11] = (s7 >>> 18) | ((s8 << 24) >> 10);
+            Z[i + 12] = ((s8 << 2) >> 10);
+            Z[i + 13] = (s8 >>> 30) | ((s9 << 12) >> 10);
+            Z[i + 14] = (s9 >>> 20) | ((s10 << 22) >> 10);
+            Z[i + 15] = (s10 >> 10);
             j += 11;
         }
         System.arraycopy(signature, signatureOffset + PARAM_N * (PARAM_B_BITS + 1) / 8, C, 0, CRYPTO_C_BYTES);
-
-
     }
 
-
     static void encodeC(int[] positionList, short[] signList, byte[] output, int outputOffset)
     {
-
         int count = 0;
         int position;
         short domainSeparator = 0;
         short[] C = new short[PARAM_N];
         byte[] randomness = new byte[HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE];
 
-        /* Use the Hash Value as Key to Generate Some Randomness */
+        // Enc: the XOF is instantiated with cSHAKE128 (see Algorithm 14).
         HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
             randomness, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE,
             domainSeparator++,
@@ -478,10 +463,9 @@
          */
         for (int i = 0; i < PARAM_H; )
         {
-
             if (count > HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE - 3)
             {
-
+                // Enc: the XOF is instantiated with cSHAKE128 (see Algorithm 14).
                 HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                     randomness, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE,
                     domainSeparator++,
@@ -489,7 +473,6 @@
                 );
 
                 count = 0;
-
             }
 
             position = (randomness[count] << 8) | (randomness[count + 1] & 0xFF);
@@ -500,40 +483,29 @@
              */
             if (C[position] == 0)
             {
-
                 if ((randomness[count + 2] & 1) == 1)
                 {
-
                     C[position] = -1;
-
                 }
                 else
                 {
-
                     C[position] = 1;
-
                 }
 
                 positionList[i] = position;
                 signList[i] = C[position];
                 i++;
-
             }
 
             count += 3;
-
         }
-
     }
 
-
-    private static void hashFunction(byte[] output, int outputOffset, long[] v, final byte[] message, int messageOffset) //, int n, int d, int q)
+    private static void hashFunction(byte[] output, int outputOff, long[] v, byte[] hm, int hmOff)
     {
+        int mask, cL;
 
-        int mask;
-        int cL;
-
-        byte[] T = new byte[PARAM_K * PARAM_N + HM_BYTES];
+        byte[] T = new byte[PARAM_K * PARAM_N + 2 * HM_BYTES];
 
         for (int k = 0; k < PARAM_K; k++)
         {
@@ -552,12 +524,13 @@
                 T[index++] = (byte)((temp - cL) >> PARAM_D);
             }
         }
-        System.arraycopy(message, messageOffset, T, PARAM_N * PARAM_K, HM_BYTES);
-        HashUtils.secureHashAlgorithmKECCAK256(output, outputOffset, CRYPTO_C_BYTES, T, 0, PARAM_K * PARAM_N + HM_BYTES);
+        System.arraycopy(hm, hmOff, T, PARAM_K * PARAM_N, 2 * HM_BYTES);
 
+        HashUtils.secureHashAlgorithmKECCAK256(
+            output, outputOff, CRYPTO_C_BYTES,
+            T, 0, T.length);
     }
 
-
     static int lE24BitToInt(byte[] bs, int off)
     {
         int n = bs[off] & 0xff;
@@ -1178,7 +1151,7 @@
             byte[] buf = new byte[HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A];
             short dmsp = 0;
 
-
+            // GenA: the XOF is instantiated with cSHAKE128 (see Algorithm 10).
             HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                 buf, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A,
                 dmsp++,
@@ -1192,6 +1165,7 @@
                 {
                     nblocks = 1;
 
+                    // GenA: the XOF is instantiated with cSHAKE128 (see Algorithm 10).
                     HashUtils.customizableSecureHashAlgorithmKECCAK128Simple(
                         buf, 0, HashUtils.SECURE_HASH_ALGORITHM_KECCAK_128_RATE * PARAM_GEN_A,
                         dmsp++,
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/Layer.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/Layer.java
index ae76922..bce3f7f 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/Layer.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/Layer.java
@@ -20,7 +20,7 @@
  * <p>
  * More information about the layer can be found in the paper of Jintai Ding,
  * Dieter Schmidt: Rainbow, a New Multivariable Polynomial Signature Scheme.
- * ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ * ACNS 2005: 164-175 (https://dx.doi.org/10.1007/11496137_12)
  */
 public class Layer
 {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java
index e537336..9ac9d16 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowKeyPairGenerator.java
@@ -16,7 +16,7 @@
  * <p>
  * Detailed information about the key generation is to be found in the paper of
  * Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
- * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ * Signature Scheme. ACNS 2005: 164-175 (https://dx.doi.org/10.1007/11496137_12)
  */
 public class RainbowKeyPairGenerator
     implements AsymmetricCipherKeyPairGenerator
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java
index fe0cd98..ba1cdf6 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/rainbow/RainbowSigner.java
@@ -17,7 +17,7 @@
  * Detailed information about the signature and the verify-method is to be found
  * in the paper of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable
  * Polynomial Signature Scheme. ACNS 2005: 164-175
- * (http://dx.doi.org/10.1007/11496137_12)
+ * (https://dx.doi.org/10.1007/11496137_12)
  */
 public class RainbowSigner
     implements MessageSigner
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/AllTests.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/AllTests.java
index 3c2df4b..0f4e8f8 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/AllTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/AllTests.java
@@ -9,11 +9,11 @@
 public class AllTests
     extends TestCase
 {
-    public static void main (String[] args)
+    public static void main(String[] args)
     {
         junit.textui.TestRunner.run(suite());
     }
-    
+
     public static Test suite()
     {
         TestSuite suite = new TestSuite("Lightweight PQ Crypto Tests");
@@ -26,6 +26,7 @@
         suite.addTestSuite(NTRUSignatureKeyTest.class);
         suite.addTestSuite(NTRUSignerTest.class);
         suite.addTestSuite(NTRUSigningParametersTest.class);
+        suite.addTestSuite(QTESLATest.class);
         suite.addTestSuite(XMSSMTPrivateKeyTest.class);
         suite.addTestSuite(XMSSMTPublicKeyTest.class);
         suite.addTestSuite(XMSSMTSignatureTest.class);
@@ -43,7 +44,7 @@
     }
 
     public static class SimpleTestTest
-       extends TestCase
+        extends TestCase
     {
         public void testPQC()
         {
@@ -51,7 +52,7 @@
 
             for (int i = 0; i != tests.length; i++)
             {
-                SimpleTestResult  result = (SimpleTestResult)tests[i].perform();
+                SimpleTestResult result = (SimpleTestResult)tests[i].perform();
 
                 if (!result.isSuccessful())
                 {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java
index b7f9acc..1882912 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/GMSSSignerTest.java
@@ -1,7 +1,6 @@
 package org.bouncycastle.pqc.crypto.test;
 
 import java.math.BigInteger;
-import java.security.SecureRandom;
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.CryptoServicesRegistrar;
@@ -21,19 +20,11 @@
 import org.bouncycastle.pqc.crypto.gmss.GMSSStateAwareSigner;
 import org.bouncycastle.util.BigIntegers;
 import org.bouncycastle.util.Strings;
-import org.bouncycastle.util.encoders.Hex;
-import org.bouncycastle.util.test.FixedSecureRandom;
 import org.bouncycastle.util.test.SimpleTest;
 
-
 public class GMSSSignerTest
     extends SimpleTest
 {
-    byte[] keyData = Hex.decode("b5014e4b60ef2ba8b6211b4062ba3224e0427dd3");
-
-    SecureRandom keyRandom = new FixedSecureRandom(
-        new FixedSecureRandom.Source[]{new FixedSecureRandom.Data(keyData), new FixedSecureRandom.Data(keyData)});
-
     public String getName()
     {
         return "GMSS";
@@ -42,7 +33,6 @@
     public void performTest()
         throws Exception
     {
-
         GMSSParameters params = new GMSSParameters(3,
             new int[]{15, 15, 10}, new int[]{5, 5, 4}, new int[]{3, 3, 2});
 
@@ -56,7 +46,7 @@
 
         GMSSKeyPairGenerator gmssKeyGen = new GMSSKeyPairGenerator(digProvider);
 
-        GMSSKeyGenerationParameters genParam = new GMSSKeyGenerationParameters(keyRandom, params);
+        GMSSKeyGenerationParameters genParam = new GMSSKeyGenerationParameters(null, params);
 
         gmssKeyGen.init(genParam);
 
@@ -64,7 +54,7 @@
 
         GMSSPrivateKeyParameters privKey = (GMSSPrivateKeyParameters)pair.getPrivate();
 
-        ParametersWithRandom param = new ParametersWithRandom(privKey, keyRandom);
+        ParametersWithRandom param = new ParametersWithRandom(privKey, null);
 
         // TODO
         Signer gmssSigner = new DigestingMessageSigner(new GMSSSigner(digProvider), new SHA224Digest());
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/HSSTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/HSSTest.java
new file mode 100644
index 0000000..5c2db12
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/HSSTest.java
@@ -0,0 +1,153 @@
+package org.bouncycastle.pqc.crypto.test;
+
+import java.security.SecureRandom;
+
+import junit.framework.TestCase;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
+import org.bouncycastle.pqc.crypto.lms.HSSKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSSigner;
+import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
+public class HSSTest
+    extends TestCase
+{
+    public void testKeyGenAndSign()
+        throws Exception
+    {
+        byte[] msg = Strings.toByteArray("Hello, world!");
+        AsymmetricCipherKeyPairGenerator kpGen = new HSSKeyPairGenerator();
+
+        kpGen.init(new HSSKeyGenerationParameters(
+            new LMSParameters[]{
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4),
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4)
+            }, new SecureRandom()));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        HSSSigner signer = new HSSSigner();
+
+        signer.init(true, kp.getPrivate());
+
+        byte[] sig = signer.generateSignature(msg);
+
+        signer.init(false, kp.getPublic());
+
+        assertTrue(signer.verifySignature(msg, sig));
+    }
+
+    public void testKeyGenAndUsage()
+        throws Exception
+    {
+        byte[] msg = Strings.toByteArray("Hello, world!");
+        AsymmetricCipherKeyPairGenerator kpGen = new HSSKeyPairGenerator();
+
+        kpGen.init(new HSSKeyGenerationParameters(
+            new LMSParameters[]{
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4),
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4)
+            }, new SecureRandom()));
+
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        HSSPrivateKeyParameters privKey = (HSSPrivateKeyParameters)kp.getPrivate();
+
+        HSSPublicKeyParameters pubKey = (HSSPublicKeyParameters)kp.getPublic();
+        
+        LMSParameters lmsParam = pubKey.getLMSPublicKey().getLMSParameters();
+
+        assertEquals(LMSigParameters.lms_sha256_n32_h5, lmsParam.getLMSigParam());
+        assertEquals(LMOtsParameters.sha256_n32_w4, lmsParam.getLMOTSParam());
+
+        HSSSigner signer = new HSSSigner();
+
+        signer.init(true, privKey);
+
+        assertEquals(1024, privKey.getUsagesRemaining());
+        assertEquals(2, privKey.getLMSParameters().length);
+
+        for (int i = 1; i <= 1024; i++)
+        {
+            signer.generateSignature(msg);
+
+            assertEquals(i, privKey.getIndex());
+            assertEquals(1024 - i, privKey.getUsagesRemaining());
+        }
+    }
+
+    public void testKeyGenAndSignTwoSigsWithShard()
+        throws Exception
+    {
+        byte[] msg1 = Strings.toByteArray("Hello, world!");
+        byte[] msg2 = Strings.toByteArray("Now is the time");
+
+        AsymmetricCipherKeyPairGenerator kpGen = new HSSKeyPairGenerator();
+
+        kpGen.init(new HSSKeyGenerationParameters(
+            new LMSParameters[]{
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4),
+                new LMSParameters(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4)
+            }, new SecureRandom()));
+        
+        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+
+        HSSPrivateKeyParameters privKey = ((HSSPrivateKeyParameters)kp.getPrivate()).extractKeyShard(2);
+
+        assertEquals(2, ((HSSPrivateKeyParameters)kp.getPrivate()).getIndex());
+
+        HSSSigner signer = new HSSSigner();
+
+        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("hss private key shard is exhausted", e.getMessage());
+        }
+
+        signer.init(true, ((HSSPrivateKeyParameters)kp.getPrivate()));
+
+        sig = signer.generateSignature(msg1);
+
+        assertEquals(3, ((HSSPrivateKeyParameters)kp.getPrivate()).getIndex());
+
+        assertFalse(Arrays.areEqual(sig1, sig));
+
+        signer.init(false, kp.getPublic());
+
+        assertTrue(signer.verifySignature(msg1, sig1));
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/LMSTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/LMSTest.java
new file mode 100644
index 0000000..fe0af44
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/LMSTest.java
@@ -0,0 +1,119 @@
+package org.bouncycastle.pqc.crypto.test;
+
+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.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.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.Strings;
+
+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 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(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, ((LMSPrivateKeyParameters)pKey).getPublicKey());
+
+        assertTrue(signer.verifySignature(msg1, sig1));
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLASecureRandomFactory.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLASecureRandomFactory.java
index 1532d91..c989e18 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLASecureRandomFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLASecureRandomFactory.java
@@ -45,6 +45,15 @@
     }
 
 
+    public static FixedSecureRandom getFixedNoDiscard(byte[] seed, int strength)
+    {
+        QTESLASecureRandomFactory teslaRNG = new QTESLASecureRandomFactory(seed, null);
+        teslaRNG.init(strength);
+        byte[] burn = new byte[strength / 8];
+        teslaRNG.nextBytes(burn);
+        return new FixedSecureRandom(burn);
+    }
+
     private QTESLASecureRandomFactory(byte[] seed, byte[] personalization)
     {
         this.seed = seed;
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLATest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLATest.java
index d35d56e..ae3a45d 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/QTESLATest.java
@@ -1,8 +1,16 @@
 package org.bouncycastle.pqc.crypto.test;
 
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-import junit.framework.TestCase;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.params.ParametersWithRandom;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLAKeyGenerationParameters;
@@ -12,196 +20,452 @@
 import org.bouncycastle.pqc.crypto.qtesla.QTESLASecurityCategory;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLASigner;
 import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
 import org.bouncycastle.util.encoders.Hex;
 
+import junit.framework.TestCase;
+
 public class QTESLATest
     extends TestCase
 {
     static SecureRandom secureRandom = new SecureRandom();
 
-    private void doTestSig(AsymmetricCipherKeyPair kp)
+    private static String asString(Map<String, String> values, String key)
+    {
+        if (values.containsKey(key))
+        {
+            return (String)values.get(key);
+        }
+        return null;
+    }
+
+    private static Integer asInt(Map<String, String> values, String key, int def)
+        throws Exception
+    {
+        String value = asString(values, key);
+        if (value != null)
+        {
+            return Integers.valueOf(Integer.parseInt(value));
+        }
+        return Integers.valueOf(def);
+    }
+
+    private static byte[] asByteArray(Map<String, String> values, String key)
+    {
+        String value = asString(values, key);
+        if (value != null)
+        {
+            return Hex.decode(value);
+        }
+        return null;
+    }
+
+
+    private void doTestKAT(int securityCategory, byte[] pubKey, byte[] privKey, byte[] seed, byte[] msg, byte[] expected)
+    {
+        //
+        // Validate Key Generation.
+        //
+        // Invoke the key generator with the Fixed Random that uses the seed from the vector.
+        // This ensures we can generate the same keys as the vector.
+        // USE A REAL RANDOM NUMBER GENERATOR IN PRODUCTION.
+        //
+        QTESLAKeyGenerationParameters keyGenerationParameters = new QTESLAKeyGenerationParameters(securityCategory, QTESLASecureRandomFactory.getFixedNoDiscard(seed, 256));
+        QTESLAKeyPairGenerator keyGen = new QTESLAKeyPairGenerator();
+        keyGen.init(keyGenerationParameters);
+        AsymmetricCipherKeyPair acp = keyGen.generateKeyPair();
+
+        //
+        // Test generated keys from seed match supplied keys in vector.
+        //
+        assertTrue(Arrays.areEqual(pubKey, ((QTESLAPublicKeyParameters)acp.getPublic()).getPublicData()));
+        assertTrue(Arrays.areEqual(privKey, ((QTESLAPrivateKeyParameters)acp.getPrivate()).getSecret()));
+
+        //
+        // Set up for sign / verify testing.
+        //
+        QTESLAPublicKeyParameters qPub = new QTESLAPublicKeyParameters(securityCategory, pubKey);
+        QTESLAPrivateKeyParameters qPriv = new QTESLAPrivateKeyParameters(securityCategory, privKey);
+
+        // For signing..
+        QTESLASigner signer = new QTESLASigner();
+        signer.init(true, new ParametersWithRandom(qPriv, QTESLASecureRandomFactory.getFixed(seed, 256)));
+        byte[] sig = signer.generateSignature(msg);
+
+
+        //
+        // Verify signature matches vector, NB signatures in the vector are [sig][msg] hence the concatenation.
+        //
+        assertTrue(Arrays.areEqual(expected, Arrays.concatenate(sig, msg)));
+
+        //
+        // Set up for verification.
+        //
+        signer = new QTESLASigner();
+        signer.init(false, qPub);
+
+        // Verify verification.
+        assertTrue(signer.verifySignature(msg, sig));
+
+
+        //
+        // Damage signature, expect failure.
+        //
+        {
+            byte[] damagedSig = Arrays.copyOf(sig, sig.length);
+            damagedSig[0] ^= 1;
+            assertFalse(signer.verifySignature(msg, damagedSig));
+        }
+
+        //
+        // Damage Message, expect failure.
+        //
+        {
+            byte[] damagedMsg = Arrays.copyOf(msg, msg.length);
+            damagedMsg[0] ^= 1;
+            assertFalse(signer.verifySignature(damagedMsg, sig));
+        }
+    }
+
+    public void testSignQ1p()
+        throws Exception
     {
         byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
+        byte[] publicKey = Hex.decode("7561DAADC32E5C51E5C7AB83FB32FAA58E32A0D1E5134A07BBBC3AE889C85470D024D22EE8A269A9772C24B5971CEC2AD55A846C6D8DD917B76223B0799227F89FE8EB91020CA49BD528FF1915ED00D9F9709740A13110864F11C24CF0DD8DEA29BF288DCFE4B1CEB12A7135C5142D7F51376456892BEDF33A295D6D70AD058C51D565E8097D26440D57DDA86AD04C1E7ED45946654ADFE9F46A2D2CA0BEA6B2E57046D734A3989BD6B2C42B9413646C1A2F1B6C3E55502D8780681D1553601130BF98D26EA05C216E7C98FF0D5FC03D10384DC6E29316921F50AD19FF96772F998DA91AFC45CB3A00E67BAA713E2825C6DC2A0C0E8778444870A0483E588EDEC82199DA34B636966A85D9001C446C425415C8F8269697EAD5D69DBA92F77228808FE2AC5485612B0F28862D9EDA5D32D5CDD840BD45649C1D7C50044CC017D87E8D1012FE2EA0E26F2710467A13230C73F485F26154825038BEE477CE3154C36468B22124038C3C0A8E41C8681B20BFA3493CF88299E70D0D3FBCB5CF7EB82D39656A27FDA96E62CC84FE9A83DA858A9982F01803872833C48376E04C4E319744EA26EFD8FD4A1834B5058986DEF96CD22F4A30652A543D640C424AD107B54EC7D5E9244328BCBD166100969A54B003DB5F1D30B7D8462941A5C2E6E166563C1D7C7222D167E2627C98EB0F4274FB098C1BB288C8A12180E0A2F32596B54E3E68520B625B87B47F5E2FF64A1490CEC86E82E5E94359FDF6782E2138229D2C1EFF49B6BB6A722D033FF80BBA7A2CBC90D40E39E2E6777E062F4603BF87C407E2C20DD67E67DA5FB1AF696A05CB301B78A91A54F0881D5459E35E4070E0FA605C43E98C472C76DF9C31AAC8A7473DE2B989F297B230B751B4F32B61CF089F77317285D302011D7D6BABCF4C07C0C70AD4DE3E99C46EF595EA9A61A080DD48C74A0C961513C845DB6A5811750145D081B4D4E76DE5B15DBA5C1626289345049D5CB8771C7C129DA1C5DD8BEE596321647E853BCF8D661D194A1023845F420066244F6BB5A0D50533579AA0DB5A726DAB32EA2B35261AE5B6D0DEB12C31A79242E2D952AA1A1774CB90AE815BE9613A93B30AF30374F5C8181F54B4F5D56E07232F68C6D52B94BA64ED1B2B7CA37EE6C730E03816689853224242F0297A017DCE955C8B8D1B41BB3B12665759584D3373BC2C77999205ED56F6E8B6AAEC3346D1528C2AE938CE21C24400ECBC4BB520958E3303374C68A21D403A6E6F05DAB6A8C03FF07FF5B83A216B2EBBF8E357BCE51A52A56EAAF6B6F6D34B8BCE02E22BB67CAC6629A8E8C3F9F4E7A3AEE3B9AED01B2E3BCB3FE566E3DFFD8C4A9830AEF3D30DE63B5208A32433A4C42633C4840EC5493E891D22F409E35F02CFAE46A9D3DEED6C221916F9B27BD7EE3C1CC3C961E59B3DB42A59213FFB168DD170393832ACA8C79B52C194571681F0E8AAE2DB99C1201632659AE136E3B085985DC94981A5503D950E041763AD485E253DD8B329283929E2132EFA6B9EB08E79AF6027D6E2E7E30E3D6B785256BA2E012D003AB5C07FC4A0EAB484991B3CBC3B03346409491A876C82548E2BB253E344E80415E4625D3BF15975B59DC36FA00A7FD894D363172DB8C2ABE40364F549A00C5D5FA8B534E0CAE6A9AE83CB5E4BE6BAEE126B755C9CB8F76C1C8A6E6FE2567C1CBDA68370D4D8465D8B360880601206CAC0C9D0B67C0261A0E7D132CE9C4EBE21250F7F197480FD5E0D8DA83068EFA8B35475733FAB080BB888E1D1D89D124AEFD81BAB3D970FE059674B6E71455517B9BA322A236D272144963DA4A934A1D59D19F05FEECDF0F1E390376801E0454971E30C47109944BB4DF414E19326B8BA2013D5F6B3FA20188E5E9BCC63ED8BC55D8D7D3A086193F9E80DD32381B26BD534D34F298DC06378EDBA2E614E8C3D886297AD1B2B59FF78B8FEC0760336C5207082D82ADA01CAE49AE6500AD992B7FC0631491B909F449618E5F82B18995BC926F46BBEEE152AB2706521AEE3574295F26FC01A7AD154673B2D3B500E1043FF51340BAAA70CF8B3A65C65811318AC7B4A32A9519A00ACE331959AED26F86CACCD7A952F9D40E66FC31224AA1BD280F89A6B16F46AC39C5F6D709868F92D0FA519D39827D215D8BA7F2FA6B4DA67D5A791984E7A1BDB04A5A7B024762952121C109AA318FD2CE88EFAD90FFCC17419C2892C916BA7E7C482ACF41E5C386290F1174AE5C5BECC5A0D21E23B40FEAF220CB9A58AF8A5B5910DBE823359150388A4B0046090287B760B3978433317F4C2E1AF2A5BC87EFD2F59931F558ED226CEA7174B9BD4D1BBF6A10CE2C2D827DC4D36B2096B9309D42EA8D45B5AC2B1F7049CCA4078D4751B6B03DB06B8BEF3E119589CA61D7FE308AEF4561C4EE87DEAC2DB94C38C4760916D27E6BB2C169D1C88D35616F8C5EA6EDD4D48D6A5CFF409DD67D5BF704B832C3454A8BD14568B93F760EC179ECDED970C46BE39D5960352D2F5B3959758CA7A10C88E961D474449CCC21394D2099F6110BEE145E7005DA5501C0DFE3DF1094ACF99951A2974808A60108643E9F4177827A3F616009FA67E21F54C363BF8F162F33254D6CFED5E7C0D4AFCF9854620B9632FBE147284C6739F5FB4F211C20B1D9982215C2C4B1C02CFD041F8281E429940E8FCB66C32B694C2611D8D7F50A751D3969C2A3AB1FB2553CF0B26C0478C2988D9008EF4A1777E9F7F16122BDF208A5044BE728E9D928A52F133C1CC462984590D44E00FF7AE1254928A006054D008E780E5D10BA7E11E17E296D43D21F64544AAAF27E6AF9D4C7A381E4CC2873F358B3541454BA2D594602DA71273F50F99201CE60F93E1431D82D5402E8654206DFC651A3E36CA854A9B18E8DF0CE2809FA89BB465C68475020C51A773107D9B367E3C9A82370773A498FABAE4AE84D7685C9329FB226E0F5F6173C5107AF143BA84BB2CD9A9D2CEF930C5F8055A4E71C1CED7538F3D8FD194EDA2C98FFF3005BE8A73D09C7CBC8DDA07F4066BDCBFF23A1838520AEE0A28E69DE30019DA6AF61FE52A6FF10B4A9215F8F9ABBC9FB5081CA33463F891A53DC91E252E90EF4B0D72A35923208CA48C3793347F3D5FD28DE56538586D2C2C6C1FD50C4160D67DF11254BB36CB94B835734F5B831BF12845155B49FBE3B0F44CC5D6782983D9478C6B3A9F30626A797A2CDF8911F5BFA90E611B39B29314C9E4CB7F12A69D6DAA1916667AAC5AEA693B70BD741928014ACCD125137488BEE48BAABB02DA3C6A9604A16BF4D19F1C0D87FAC94840C721EDDF03CD8A841D2EFA5D52BAFB50726B45A476C47E251601B0127C092A4EB8A66A0CB7A2DB5503394AD36D54F37CC6EC73317E38AF9FB62BB758973971EE9E75E6D8AC67A0D44384439E7B11390E983975281A194F8173119905FE8FFD863D55CE7A6AD00683D4FF83263C81A447948DBF3F1EA2CA593B38E6719A8EECF654774599AB25D1EC9741AD3DE4DC23264C9700CDA27C463656E3A53C52B860B3264CD9FC0334D915E03EE74E54D4A2E2D795297FC748075A404A6C0AA6A22671A480AA3468B9FA9DA9A20DFDD8BB9100C8B6804DD6F16AB0C1B938FBEDC2F1A9DF43CC877B0F323042E9B1DFBB8A2BDA8986534D52B7EF06614DA22F63951716C2B04196567698552F3BA2067B2309AF06DB126727E1A82F731B0CC2904470429C1EC94456D1E66A0EA35A2A1C552CD3FA1A99AD3D748D563978800C32F5B6CA195938BA26CF721FFCDD9402EC4E37A9E1198D39516B4892919327592C67ED148672377A53C7AA9BD47D16282D977A40BB7E036A3FD11283DC2BD82EBA452474B827434014D0BF7C5483F28A432D378C6C78E6470F4026C27F531C367A2B950B0DD95114A106C72227CC33779C705B1C8926DFA2F996000535A0BB87BCFF4B9A12D829D04434AA579A11F6D15490A2C9FAF65042DB4CF88BE47368C589629703815FA6A2A1BD4A1447330E00CAE7645B2527FB82E864290C9574FCFD364F1A4FE3419642D24D98A6A33A0FC0E83505FD5151518555F9E03B2D5742630A119B2B32657AD6B2EE8B624508780F98B27E51BEFACBF3A7B920A282EA45358FA4145AA24E7CDCB3432C6F08D92421E2561EF811F1C60D7E51C840EFFD637D940F43D655FD1221F2A41027A71D90F978CB09E2264737053330E47E4C433F9B5AC94DB9B04EF7326410CC60DF7A220E1749A654FF6370D7C8197C808D35C2B6769E7CCD009BCA5835E4FDDA202D6C3DBE324AB97C879DA16184123CEEC3D5FDCC3CC71103E46E711CF8274D29B5CDF074BA889D6B0E51290428E0499022E634439804A488483D3927DDB78C248EBF9A6C9A678F4F482373580CB7FE2DAD993DAD34149F112D735C7E60DBA06BC44F0304551ABC09B9C91D4211FCD6025A865C90159CE3D842405A5143C46BE19DFDC64228A413D4225D7C94697C0F128B4336E9483192F3024014D2E9885447D0762F802087C7654786F76773DADA83CBC7396E5DDA29BEE95E93FD8C67D75C7D258765C7154DC822B925E8684DEB3443F85300C96E59192E8E99D2BCA21CB62713CC5EB23BF156FB3E97DAAA0695D84932FB9C9DB03E0BB13B103190246CD9AAF79C4B9C8916DB4B6BA02F5FA8B87B9E9B8D18DF6650E907E8A02BD67B4C00EBFE4E4C2CDA560D1874D7215CF0009DA57529EA831A8E71C88152849A8C2900134306C0C05A40BCE42A17CE9C025C100277D7F0CB35566BE5AC11B2466A2BB03B8DA8F10B7A30CA26FC4E0A3971E16DA0C5FCA52838361046549A77B9721027DF307AD3DC8B4EB3DC05BE4B0B0FB77482A794305BBE99499F6ADB05519D1D4A12740F91A363602232535458E94C7487DB90E777247864446B5A862164D9761DAAFEAE213440958E0C399B4DCC6C6BCCA44BF33512828B20402EB814384B9ECEC2B0C1316F3C545D4A65F31CC01B9E324F4468ED92896AD4572248E7298520FAAB161203A09D366EA8FB0041E8A79368822C557C67889DA58C63043C0B6E94B36C61CA41E5BB570448894EEE09F4F00A18A0164FE451B4D005AD5B9A1E3013C356652479D340594223238E068D9DC8C838902D72D97D35EA16E9B7A1852EF464E22D6CB4217BED00F1998134CC09098453C34CB9C0EA8ADD432E295A4B8DE30BDE1AB413F515153A75D02CE99B2EC8E45B3FBC1682973B769D688C8F6FA1AEBEE1795D1B9563AD1D462BAF1668D0426C457382E0EDA9A898FE694D319CCFFB53F378EBDF3AC7BBCDA8CB319470FDA31A1E4E920237A31A7F31392F54BD8991A0A98C65E8DCD9A9666B4403CF1C47B76D30391610161253A9057D23712ED9C71816D1C4B32A390AB6B4DE8F6D6032B8967B30471E80E2FF925DCA64887EA29FD14051BE7856B65B954A37F9577384245272B2098990F198EE1A9ACF1548299512F541E45922238F31115532A3AD6D744473F68DDCE411BEBFD933BE64AD1C7007258DEC9B112D4B9B5ED01FB4DE74E128C90BBAF64763A540E41B407151CFD8C2048A415304F05F7733238C6722AAB433ECE0CF900EEB02C24B20DEB96441E947ED1097A539C6A1B9E8B838E6CA2055ECC7064F43495815DCA3972B83401D5C8EC61708E692130512573672CCB5B9A56973E2EDDB1E3E1C965CBAD617D2590C277B91985007493DC9909BD6BF8578F2127141993ABF817B6DA2019DC6CF79207F96AB032B6D06C0512D049EA8663C46D5EDEB246F036C978496B1C1BC47E81CB63364F47B6913D0548EC13E1A37BCE62C68CE98BA1C29E71A3868491569B3093DAE40D42A5ED2972182CEBCE8F2589137699B142E3D3D7164939A081FDF237AA58ED794603943F2269A40B2967314C22E8668B15FCE244E145A3A49A3CE5BFC2C46ED93F40816A92DFB2880B5C4E755B2CC50914583D0FFAA28FD934E763118DA70C3D4137957333927FA4F0F637C4C52B9078E141089BCEDC7B4288019EDF92FD0ADED643C375E71C25ADBC8F94E04CA5A4E91C362D1C86BCAD31149636A895F443A491E6F558F9DC89ABA27967C7E66EF9C7302C100E4AD175021CFC295048BD5CADD8E86469EFA437034D7A01E38BDC64A6187F51F6D023CDB240F9FD0053FF7258D2C5802D1FA1C39DBC9D6D0E65026FCFEEC8205F77FBA1F64AA03B4D1C091AF43AB800839B4EBD378B28EA895111560183199390C3C6706EC35A05E4118A192E5A29C77CED86919C2A74A827913219B2C33E83249175B886B598CD25253A4B07D10C3AF92060C52FDA89B880923595865CA108EB0E78BA3F9E7B0465522CBA8DD6094131045E13A4C979B07BE8BD782AED050C3CF7C25ADE3C2A1CAE81738C55DC1ED0A33020EB6DC3464BDF6C16CC8223161991C62D149FCEA52140198B36B6379B47F5AA5EC066A5D5F561FDAC44EF3F967EE0E0A420AF89E22A053119DEF0D36FEAD69FF1B2C2476561DAF68D7682BD807CB4D2782C0B7FC39D7C6494BBE622352B534C21BFA25E6A37613CA73B85C98894EE1E588F20E73643F241F83003ECCD05B56255AA36782EB07283337E99144CD03B90680A048D4538D580728C0AD59C082104CD79099A08C193D40C3CAAFE3DBC968F9F8D9D0C74A7CA135F02C8F9C18380B00ECA21AE7E7A22133C1A65687C7F74CC5FB4BBD291183B803B1AF8441900134932217783D9EE86A565C8E9D316E0BC93E75941989C7E227D3170B0F78AF6F1D8FA70C697B4645A5F896B0C4971B6544D0C9786A774179E99EB4425C9838D57861C88E1DAE2E482D38BAE9D9C04DCC71D97449B13D3E9F6A52D81C91C0B02B581F4392C4EEE0514D4D39C84F323D7FE9A30FA87C5380913D3955C210B84444FE7CEDECF821D903E2102E8CAEF4020006F67B6D4B2146335AF10C6EA0A116A0110C5B9C935E349A6B29E8D609361B668E42BC0CD1E33D491D59FFD4A13E5B7742A8DBC918FE7F72BF18268121CE414D222C9AAA0522CE0487F2FB8545D19D7911BDCD4C81106EBA729FBD57365B1DE562A07660EDC92337C9A695F4DE495FFF27D63248BD4E4A96E405E39999A8AE35B6CF12AAB699FF741E298A3222D168EEE5ED1C0A372810A883BAF50E7DECA6F146F6DCFF1067EF4187AF79B70D8814CD50E39042417B4A1B7A230FE68504352093E91073307AD525F069F15DA88856C5140F8E89DFC8861E8678067FD054B163E02203E06AE92FA76FE2B68527737D99F2A13893699D7985F2F025DC3A430555FC23C9CE0C078E83CBCE363C5B5840FE09155F433178C46D257F5DBCEFC6AE9140F46A78872803B8FC6D0459C8A26279E1AC09711150E7F58F420A6AC2111647724DAED3867FAFAA58D4485F0265F0FF6A63C9F88CAB0FFBBB3579DC045903D682070C4DF15D3D02650213EA7A7D2FEE676BA92F923C1A0CBB2DADE5EA8F5623A548DC1060A2BFDE25AA6A09F80D8613D9673B66D2EBB124452921200BE54364DC705DC60BA75F2D0540C4168E6CDA16DA504D63068DE764E1823700BA4C97BF86058DDD2F5942DD5F4D3B19D6883412B6089FC6A6DAE5FAD95C3175AD53CBA016D59FED8107D0C64A1B50B1BC1380261A55D04998B99202078EAC318A36879D5C5AAD9299134EE66684179F8CACC52EE8106D23D0509D32E0459384B86CAE143B3E3C0EE95C8ACAE8D496E59131C80CDCA98B812E0452B8B9EFE94DE5424C50B935BCC927FF395D4D141DFC89A639703632E98451182D4D072A987A88B4409E0B5AD6F0CC62A5A10C3DC00B03597D1453B3257770E36949218F22D721302D3CE5DFC09D5604C943F73D75C996687931133DC4B6B061E5E74483FAD6EAA9E89D9467CE4C4FA061EAE71B02F3390DA9EE49D0EC92097321171D9771B1D65A4B54130345278FF8BD415FD5C9894B9F430E47F2967C650E376E614AF5F32A9456AB0C61A9278109404A484F28AC568922691C4C3965D465CD82FDFA330E3C9DD9E65687AD8827D18617C54EE0E3F6BF7E9FFABE7127AACD1B71573E2846AADBA44415407013EC01C5A7F70960870D1742DFD488B81A34DD7A286F4CC2C000E042352CE0120A637250B2A21AA998CFD1CA99BA21C321335F4A9222486AEF986FDA32C36F64172AFD225B5D358B59A06FBC1D388A99AD5780624E6A257A96910A4471C9ED1CDBF4AD41C74538A0933A460FFFA7B36C925E02D45D89A819725B8029742C189E561840F20EEB86252A6925116578A705FAC7B5B9A128E1B429E321C0B8EAC6B74C9590E6B02DEE15A0AEAB25B7D5533BB7B6D9299620BAA42C097304795B71A91DEC14A32B96438B0D4C313DD9A25A916554D2164D787E800B524D7E02215515244E7994C427574CA0E02FA18244A81E49C03C59E85524C8A76B77828A938A0BE90C77595567FD2619381FF0FD601777B7C226AEFF35E0B5BB7D6D88660027ED4E5E4265B2E0A775122A2E375F08DC24F4F327C9C2766F9CD8DF228AED7920250F1730A4A7D08531A26827EAD62E1BAA0A45EA9B8A26F5EB7CB6B36561F72FCFA31BD4509FAEC210D79E0E86B3048F8E629DABB24AF46C73547D40C0BFAB592078383832561F15DBD90E4073BE80079F3A489BCD51B98278337077F6791CAA0AC828A14865ADD78BBABF1113A171C06D10796415C7B0F4F3B732745BF9BEC82866D4508C1930B22E391FCB6D68AC89EE0D66A8B76BEAE525BC89AE9FC4C15A21169B7FE739DEAA80A7F19D5C669EBAC95A71EFE265A1570400315B8E853B7CB18E33E3ACCD4CF9E48546E36422E6E0ED282519DD5280FE5DB8B7877902188B17C4120444D729920963CA5AF34B94E365A8B0AF7947142C114C3F0A7FFF0714ACC3833CE4C0864185ECEFC7F36736F3E267DC956B0A1051ED765DF0BD12E1D25760A09EB05C16B327A30BAA1EA24552E15448BA1B3E1DF0E8550C7F4CB8240DD4146AB5FC002E33B4FAD65115439B10991417068B09AB2EE5B16D02C250A020B27580A9C1122604D1D69CA74CB478582614C56C4CEED92660B643C50712A132EF7D148BC27C4D1FA1E8680AE171CA8643EA4BE9EFD4F442B1BACB36221370D2DAAA509F78B76FA2611744D7617C12CE4B73914A26A0E64D15E8201C5BE8313FE0B0005102EDD25F95B440C96B90D9A92720AE271B542060B2EDB0245BCB3C83F901E2631B76C24768D28C18B01DA9C184DEF925D68C655E65A013B592D161841045F3EC54D0088449A64FFF0EA8E6AF632347BEAE431C5EF288A30AA74D95241EE7A6FF4E73DD22EAB04C6A6339CAA3A4E8C181E0751AEE8468771EECB0AAB3427493B0DCC2B19DD9A586DF437348C7CF9C5E3A8B1A630D5BBA6307CA395B4C797CEEBFAF5153258B325FAA9AAD673DE591DC8C0D589C61EB70CB7327B537AC1A31A6EA95C81C886CB3DE52427BBE10736E1BBDE9B8EA4AE6A2B44E7BC376B27C08E6FBB22A73B8D14CBB3BDA545B9430C30D6F3D67E0B42D7B122DCEC983B996188F79FE193F03CF1B446F166C5D8F2B1FBACD5C85678698EED8E5C76E2E0D55B955A2643FD1BBBC16D5F5BCA479AB5C1182FE2918BFD722DA84A010E60164C3ABF8137D293DC8719731F40ED404BCBF171E99F511F1E3CA723D59C34D78EFBBD463403F1E87337CA461447FE180EC53054A3F969218290042FF30D798B0C20A84C12A4D632CC9955FCE0D5DAB4AD68E71E5E4F5E446DA5FF1E2A5EF84BA2A3F222E4240355850B20CA5065330046E261E88156FC1A84442C60ECBF4C3D2242E27F8D806B022424D917CDA2830CC401C7AE9F1221FE533EABE3CD2B45E5DDC9379CEC4542B529977E18A8CCBD41B827CF900268CF7F6EB88C82415BA10930D1E157832F46E1B0F14E4944B720270C92509DC0DFC24F605A8AC015511B5554A4170F97064CFE6CF944D3B202A7C17DA356ECA45D92941C661B70B4AA679EC10DE7230C095F0590575FB28038E02A807ADD262BE38F3CA3F5A1D103EAF88EA59449AD040FEF17E702D2E46424A19216F2EA716B60070D5F8069215D580B296EDEA56FA3049972FA9A7CC947E1C56F669CD228B4C0446268BC47FB0D160EFDF7AD3218D3D57BC4B583319CA48A21D8D1BCCE5179421F776870F140CB9F18E2AA1DD2C5F877D91CBCCED0B4B8D84B111AA7160EE85B2C9B8AB69C1E3EE4431FFE4D3BD6A4DC6C8A800BD20CD8137AE1754BFC244CA7C5EC281381A237548A9D9AFFE7100B4254302EDD5461122D314B4938BA7B2A4423B40142E090E7AA3A6FD08578B40B17F189F9B16DABCB6DA64DA5470991398E9B3238DE4622FD2D12FCBA1928BC861ECC336BDE65A5BC43B6467CE293D37A1019EFBABA13ECE121B0ADE45AEB7C644C2D49C05B45F946DFB58E78B5373A4DB14884A241367672A563E3242D1856C8D1662D5F3691A6F5C769DCEF47778685BB448A8ECC5179EFE36CE6C7E5C04FB2644309018C064B2803CC1377D19F59D30B19E04D2981873CECC5C3E992C8889708B89E1990AFF746B340AF895962FADB2E2F926342137DAD8C1EBB09A616F658F40EBB28D3858D21B9F92A7FCE7FF2D421994CBE6072BCAC3E0DC3A030A212219D5DBDC906B07DBF29DB8EDCD213667F40CDE4AC7FB56E2A08A55EEAFDB4E71196FA642828D56983AAC268FFDD7E5BF02CEB2581B6DB158C91393A36C367F36AAAAFD2C40C2B4EB40963D1332155353412A2126C51BADEA6B842D2FE695B3CBA2E2584288304015905AFE6F808CF58A708290C4824CC484EED112384309A28E028D1E2EBD46CE419B5CEC1D9C9164C9A40218DCE50C9B466A8E3C357D3F86EE02A51F86FD361C000509AFB168275DDFE1F1BDCCBEB92CBB345E62CCD4F0328FB0A35FA8F28919927C1443D289AF808C8F034D28F79038DF670499E6521F080F0D365F2400D5D086CC7C0041D4DF80E4E4CE3906215D18EB720466CF8B43DE1C692BB71F63DF253EBD498A9020BA92517628263F65B8CC41A5D21BD76B6436029FED17639049766716B33F03E6F1786852AE47250BE2FDF8B6BC4F0084A823256B34A2C2FF4931119139FA60DFD0F376990DFCC08BA8A09B0EB3DC299CE091A1A15F1BF7C11FD35CC40BA9C2C624530441027ED45FF8C00EB0B2BDF7BCC18746DA7942C342D2A58A88EE549BD71915D1D907F0CF89C6E2CCF63025E0922E95E713AD78DDB8052E17007A83B966279E860A19F38FA484702A1229ED9BCE918B360B2C1EA70ABC182F62B2B6306802D70E4E2A83D46CAAAF950A1A20DBFBDA6ED1F21AF9C2AD2092875EB391A925951255D9FAE3292EC485B5C3CA16B003FEE565110E0A910A010C499AF322039140A83B28949AA8946FACC77ACC38CB4AE9E2AA250E34C0E63260BA989181C7470E0BD484C17704907A39EC96E2F4269CFFC95F311DBEE98360A999FFC28BEA9F9446A00758F83B5D492B602084DD47400864696BFB5119923EEC8D5CA82CF9F2BE623A7046A741A8F5A31259A37247F8EE9CF5EDA91B5BC5DE61AF067DAB0CE4C6C9881EFFB4E1D40C87184C38CEA0E07409AEBE92480294B852E21272AA71463C86354A37704B682825E1E7A1FB84A3329C376844286683055AAEC5C8BDD71BE9C88B3ABFA303E7885A0A027012B2B8EA8B83BFB1A8A8C4EC028DE82CC11CD18511E3F56137D84D326326AD2E0C479513F5F112E91454C6CA904010D328B94A26AFED5C8F14609037E17355E42B24222E7E5B1D84E61895C45090E769734E07AA03B00D08A0ECFE0967D7AE09D16843D54F6EEC12584F87EA7AD832B90DBBA8E39A0D21E867E3786D8FBAF19CDF7E56E9E9A44F421DD350D0D85AE095AE2F2BC0F9C7A8748279720BB0C0C6E7919CB83C40BDB3EAE1A6F87DFDAACCA342DF1C73AE02838ACCCAC609504A12F720356A57C4A3D21794A8106139C27503E6A6C47844962AC9FBBC0244601888B6F2CE6C41F8CB757F3E202F8B9A27D3057272745198A5CE7D2A4B227EA9CA6F0838B23EF2C66950F38693161AFBFD724EEA187F9D324C970B9F4BCD408161906A6648D18F4E6D52123F02694E39B6D8D1BE691C5AD20E793809F4D970893DAA133FB310146463EBF24617072871AEFC1CF9E885923416128479EDCCDB941FAFECE56906243A74FE19CAD9250141AE8B4CA6A343926D9076B964733D8978A109876475EC66691F659499CB552C28ADB0420E335F01DCC2BF041BD438E830FA09AE673C03930FF478300E5C1FCDFE6932AEBC289C3713A08B3136B37798527B2F67803C705107F985A1F4584AC2FEB24D195234DBFAF4097C1A1A6C50B6CBE1D31CEAE79299782B7A4482C21D1ED48EAD643681282282B1B0BF1150C20364431F62224FFD07EF87B9AA4CC4F73FD2284BE1064682DE6851DB21F786D1B6F73654DF04C06FB4F7E1CEF9480392AA851DB4308EFA35A16CF35B59DDE63F6208B90C776BE38CE0139E97C041D508DF432CA1D399910CD19A1A7C65C09A6CA89A5323D205ACC5BC75C312764B0B6599E10C2B8F4DAAAB9B298CC96CF3BADC21E4B797B52AE793B1BF08162DA0D4206FA64D703D100580435854460EE5085BFE241F8E149007EF2A373E2F48E55112AC1394721D0CC272C5C95D3EE9120A3F9F7758E5BBAC508FC5C20665132DEF552A7A65D122AFCFF8B1268ACB094DDB628D39ED6A5A0B86FDC96A7843C3043C083D8C06C8016AC05CC517BAFD15B6BC3EBCE8EE5F4C6D4A6B0722075586C66A769F714AEAEA49D6A0F5F330F8EF78EA55DB2FA5C4D057D3A1E17A92EE224745723BDD8FD7E6EAC10AFE9A0E22B702CA226F694FA079A95375E66B002F1E3C72C3CF63AF044C435A7EC2AB93570912535C40543A3B29AA9C8D07B0434443B32DE9C64B96385390FA0D6DFD80C2528697D253B9E7FD3AC3E5665E1FC0F707B7624FE526E5DF2242CB30740B6A965D82C4CC1F56468C8B43B4070C59F5F13A96E2D46ECCABDEC4702D1E8D57DE00F17200BF581760519FF2CA755AB85B1E6072000B0D7469B36C1D70290512A43F2B492C90C16AA08185C0920EB642054BD4D968396BA5393CA0364224CA7CE9EC77296E0E0108C691BFCE7B312524025B51F62C5A631440A21EAD737D224A9BEB8354C6F7293A710909A4C20E29F2DEFA90DC3AFCCA6608CBF4BD37800A4866A47DDDB7D6CB93E276CAD4C0BC6363B2204B8BDE55B1017181A57B3D8C21A452AC2E90329CF0A57734FD3D566CE07DAAA277390C0ED6A5F9E004F6A21AB57A29EAA2483421C06B7F26ED889E8FB5EDB83D7B323477C71CA2747065C8B6F25319E56CECC956A0F668EDC3667D16D2F511BCF247D1B0A0CB333741C2CDFBDF42E2397B92D582B2E2BAFBE4283F482460BC4EF92285A6C6901FCFD5A4207AB3ED31F0F96F887A9AA3106A11B8631DB062549A389BD938DAF7A78990698F57CD28BA283BD1627E33ADA7A5FED8F99D2916EC6369C3155969B420BD3226C7176EBADE1FDA729F841FDC40A4B71AD06CEE87D22CE513F044193ED05CCC89DA0B02BDCABA46B55077744E5CC67400CD2834299A5117C73ECC94BEC33014AC80A44B213E2454429E87186DE2AD19FCBB82717B410E17D53E0D3DC9E19549440B254C7476566B5FBEF170D9A72103FDECFA8C9734D1288524F5A14180BF729E56A3FAAD46EC1C565401646B60C319676274BCA3348D8ED8AD4CB033AD0E60F46E9326D7905EFD342FF4E62860140FE4EBB0B10A4A50BEAEEBC69454DCCA565480F0A343E69D82DC782356F4082E654245E50A9EB928B14A12C23554AC2BB690CDFF0A780F8B76450C894010C0AB69F32D1172F3A4093EE363285A1BCD8A28B652E02B0AC6AFDFEDC2FC544C70EE216805EE18F90FDB9629B315D2AB7DD2E4B07EEE3813802F131C9E9B1D28E566F9F92F0418B4D5D7C8F04A1B01FD4B311A2AB22B0663DAD07F993FA403F7752E9C4E8EF2E7D5B4015344E43914150AFA4C44E1761CAE104A9DE91BC81B6806264B4F888B7F24AB645F2F51D745C99FE1ABE1E733B8F9A54D6909E247E098D201293C3B70069D683DCE35AF0D91F490B1E439583129F45884ABEC6B25A5F59657C4F74846629746C48F25FBDDEEA64C931E3B07082204E35D3CE3DA543131FF6A284168D245EF1196B5138633030286F64B43B6BAEB12C266881E44F25B5A711553C8B30EB66070A12EE3B9DC78E9E91BCB1F5748F40DEA61B36711588A6A14060921D6776FA84BDA0B585A7CC1A855096763E91AAB01228F7EDD01C53C2EA0B5FB420CABCEC9A1EAB94EE0152A5D315AF370A1C192CC621FEF816C3CB4B9344DB176EB226138787C75280AA061D77ADF001267AD32C146C21CB02B7F0557624335E764B1BA948F81FBF69BD29641060EBB9AC541437900CBF32D8E206695E496E4747430243DD78D80CDD17320EADF225BD0CD0D158F95120AE130EFDF13441BF7578F4DCB3964DD9B507B95B1CBA60230D356C40C51A5D457F92BA19148D6A6E3F459A78A874921CCE17F654B72574398C481BA54976F940C1239CDC045F953EF5943BCBAADD35B9D3CAD3195337C4F28385D61282F97C8E1D63BFA91160F254EA157E7444E98DD5D871B839A9042E5E815BC681D6495EFD3A017806E8C28C4E0627DBF61D1240C12BBE5F08C3CEAD6A0635EF7CA2E816C56284BEE2E8ACEB4B4954B00B44DE006633AC5840A26D415C94A25A88F5B2B7B522669A9E9DD142CDC188B20A9D545E307081602646AFAB9A32B725D66EF158E98432BC2D889086DEC63D7C7B4BDDE13B3187FEECA3488AA1531E606839C04764902F0800955C6833C00FE3CCEDCDBF75F087227BF12F4603253F6177891F37D408D6D27EACCE1512ED2664BC87540510C43A1A89D8AA28C8DDBA97B1BC92DCBA9A68DFF3DC57338DC45F891BA704D17A18DE5E142B09ADD8F97A3A1266B251392C0449561E0D4EA4AD1B84D071D28038D5AAD50BC86CCC3569EDF18E5B24046668106D24767781EE56605BD41B1195D8470581517E3FD8109C0AC24448D57C146CC5D1335B140B9B4286AC0F6D2616030C0458BAD9310AB6EEE683489A1146F7A08E406F00A4CE8F514D4CDDECBD01EA05977F55C3871087B03777D0149BCB81E3A456510014E7AF448E03A36855777464E463261F419870C2E54A63B7E6D1BD9ABAE22855469FB9251394F58329F0225320C52A333CB228BFD6A0EF48420DD3C7C550C22459CFEB0073386DBE032D31C5B75ACD4F8C67AD82C71E7183969D20769A18318A725D1F22ED2BEDF1E9D72C4A8C240943370FED1DE7F23513AEA2AA1D5AEE27AFDAE647F03D98EA7121891227C9485A0A3EB5C72C263998B13CD68367AAC51CAB3CDE76674341A593B05546DE545DD0CEC049C4D445DFA36019F1448AA432ED1C0B70D11DCD69F188F10127FD96984DAEB3D2A7A3790196729BBA73443D109B38A96E394C3419F4E187EA26D474534588F2E9124E52E1001116886D350A2CB3C6CD3881FEBAEF336AC1162C84D0165F14DF8011AAF3587FCDE8D0A19FA260B652D682F57C65C3443AB602FBC27B8A0622C231880B1E62D095E1A4FCABFF880D86C3C4ACEA5946D981369678F4E34FD08A4625D9060363F22526D4D8DB175D6D854A9D15609F704322440AD183FE6FF3F4250213006D91FAC2985BCA13D4FEEA8020C8DC6770F517CDEC026E125C1AA3756A34C8CCCA219B0C214303637010586B8CD51DE2E9A29DD9675411C92540822514F10205820A2D6EA2EF3D742635D81A72AF24CE08C8FF0DC150219CBBC550AA20DD7797EA683ABF681C8042BF242C2799E55EE2C10577E3FDBB413B494447F121969AB56660700E260C31A01B88EDFFF3D8308940E295DC5AAC44CB9CDF672B3F7AD2AC7D7AC1CF668F913A64B8573A60947437E1270658919DAF571289CC5F2E0E7BDFE3E4911582B540B0B36732C5550358BD2738C70721D3C18A5BAE95A3A4DCC202C5634C390F1CB489CC907CB4DCAFE6FCAC299CAC3655BD7DB669DE2AE6AD77F3F78B4A6A154D7463944DA23515D1AB4C75456D601736BECB027B0D544062271A3345E0C9BF9333EB1B02D5038CF771C2EAA1FD6AAF8F40E9F077B907827A31193AEF045F4CB5C24A9F490819644834D01B57E09EC21038A2A701C7D21E0E92C67F3AC8151F9AB1442A9C122AD961DA84679584324F42308C384FE0A844714201EF64C0602EECC06C45FE04BFED8B664930A573B95FD7BDFDEAE6E6CB1ECC8EDA92939A92937C574CA50BFB8E6D4F92D713A4A0C3B9B098C2B45DCCD2544369A923C73DD95B6743D96549F9840781D6B0C1876B2A407640024C6A823292E4688D0A8F83EF689D7BA5A31F2D2A8039E355509F0C6A199D782346FEC75E9D9C4E5BCE1A1E668AB0F383E9B40545A36C4C90F92CCF6032DDFA51EFA03A1A026B53E092842AC49858E71322081457CC6006D81F0C3693F5892FEDBFB32290AA36B1F68199FE655E3BB57479012BC0090E58E4B9D6074A5665996666394ABC542B063174D67C7038172B85B646D330A152FF451DBAAFE2A215D1AFADD79CB9BB785E1B9F400C8DBC9F90E5E2BB54AE66760C0781C1233A33220521096AC8C5094FE49E8D631EA05188328C9732502ABD8A0EE35C8ABD039902A5C8A0C7BBFCB32508A2591731CFB1632F39DF33C2B6F36A6331E3DE35533A3CF7932D417E753ED68F1BEAF651465950B455F292A23F3675DF243043C727775C9BEED7E16789F048AB34A99E9F86DCE589211541DE582AC1B9139B25EA1F7862808DC97C598BBBAE330839320CC36DB8ABA774CADA3711DD41D44F9BD60918C2346704D0B591B76D8FE88C523155E3A7C24B388540C90B3097F4F39141CBDB541DE113F726C102B11A659E403C9AFF464A21AA4A5B3ED8132620623B48121C203B94A32DE0A42FFCB4EAC69FE2D7DE4E144C8C071DE51B1B11111E139AD656552A757DEF878844B97094A633EFE041288E275CE020A935D2F0110D09E7A9720E04FB3FDF8390B73CC92EF9378B802C2371A2FF1F396D95F587D362A615153AC8F687D84367B2C72F24C6A92C9B7119A92B95D69CF4BEE22A40CE1D0354551E2A867C78A71E38EF22F82240846DBC93EF1BA85D152752E9AF9A1ABF3896C1B36A082CF633645EECFE133E60354A05FE5BE0A16E843C8B9DD6A290F0FE1572614362B0E08C9569CCE55ABF8DC821356629F5313019D52496F7BC794BD2B1ED61260FFEA4D9BD07930EE2E66CAC9464710B87A318C6F66242C0764005DB8F9502046582F0A99B13B48A00F8796E846B2649E807D6E4B66A39BA514E07D53B0C5D85AB54BC30969E8CE0AEA37F5305F6F8312207B8505BFFB78DB0A29265D2DB06E0F617E626614371F8C49208B501ACAE806E3385458BCB52DB4D182B8F39ED5E4C2775D0E5882C189B38B4ABC1F49CE55688E6A044AC3717417D96053D0F22491BC3F5D93FA197FC246238864488B5C9A334773B11878AFB48D086CFFC5C8E389EC3AF54BC44F1C53B5273C8851F94D17F841F9D99F4EE8DC0A96640FDBA3B43F3B0174A44A2A8785CA45C1CC4462B4E201D21E3C749AD482B51079C76D44CD0264E074A10658AD6BC803314C0568A1B73699C5566DF80B5A6ADF40E80C5CA3EE7E4A989D4C40E6E043614F8829192308656EE7D81C7EBC53001640345FC9CA1D14CD3D2DBC4CA88E26224162918644A34EBA89408DB7B8B810476E33369014BE8195A7838BEE3BFCF49DE0CDFC19D50B57547879514D53B38CF19CA5456CE9DB44C08FA05E55A626984DAE33703DA1CDF56E277F20E3A9985423C9CCC85287D5BDBA4823CD95AB0D6334DB939FE601A8F1D18B60BE9D010B6D3DC14F6E2DF2524254EB0DF39895F498E4A9C46FE0BF5EE151CA3401D4DB116336F456C64E1B5349109341E94A852861D152FE10C400B98F98A7FE976852077D44765D191593723DED3E17A4DAF288BF54829DACCA038DB37070BE05B968849D8EF747A157AC51F33A31E1934574E6EA09220547E740D4472F70BA581E88D15C8C768F51FB6B006688F34BE85680AEF80B6FF74D1B84545EA641CEEC241110D4157E4BA521235529CED6A43F101D5CD9595E020E01019D819C214962576047D923CFE2EDA821DE59109C845DB58DDBBCDC2B0258CDD0D0B3D7833EF57CB9935F09044D50928B665E6460EA650634C55FE146F2B784331B815C36A9C966C9020C5A2FA9C351385ACE18405CB0EA0039F14F11EC1905D0D273C57E3F8FB218E5B3C5395BC8BECD356D513455FC70B9EC32598E677527F403F9D3A9F27FA14788D6D29F28019F44E80E2DA4787B13E60A43C8E66D81E53C3E42D02FA188C916B515D74C20724B2FBFDF902C720747481A4E268F13087900ACE6C33267CC83635E0B3ADB698D006801F967625E492B2066F6688857CBDF1452598F3319D9ECE68A85C1564A343B52913C8C09441E13C2D5F3BC59D61B624E9D671268708EAE80D74100446F8FE14A32E7DC41C4439793CFF61893B5A4D39883E99E0999541A82AF0DD8B93C07F07A07DA922217ABDDDD481EE8C5A2D6A6D0623AC3F2ED28116EA5C7ADCCC94FF0E201D1C989DC676D4043F33F37C442DD20C9AD459BCB89C2C50B453E6A91B087F3DFC72411C80E05B675A5E9A6129D0D145B3A119147FD00D37D92179B88E3AFE4E77F24A008A3D4B0DC86FBDD2773B53124E4DD5B30B08B048639A75C7094F966E81612D8C0AAF7E4B03E02E2930BD35F8506CED8B491C8038CE8C1160B5B4C11A2E57FDCA4F2B242C7F8B74E18C8C77801F09B277D4FF68FF40605A286801F24E50106005ECB27136EF343597CBDE60B132A57809D1F1FF82310E69BD5A8B851963486C73A681F04473A00E137737672B6E592572EB162422A17752CAD3E4D32076A49125B02B090731648762DD08362853C32A000167E8B32E35A372EA9D6686D82AB35656A4F4E00A6942CC4BA40E468A32C013C4BDFA76C73103E930BA5C75157B62DC3779E779ECC0956EC7758D83604D480590F88FE77B1FAD5AF7472B4905DDE62C59D14757ABD800EEE2D88E496BD3C544397A78A041E9161CB37E3F4E7B6E09C424EF12E9068B898944F53908FE800CAE8C10F625D800E0AFA6A53861E1A1F7C12E2ABA9E0D282EF848BD90C29A90A54AC5EC53BE5CAAD67C5D276E0C69C06862E1A79B9736E19C3AEA178BA8B0AE170BD304C4319DF261910B29B4B569D1B3507BF87468063AB4A53ED2D651DC7C47F6B42CCB57564E99DE5DD438A8069C8967E25DF402B8929EA8244AD67CA9B8818A03067B280A4DE2E1A1A22995BB778C40A704B0CE84A9248C7B6974970513B29AD3E58880E15054BF028AF26CFE30ADED531EAD501F5263F289E6FCCD67E978434296C52F42AA317E06C66DC4008564E971404E17AE00170211943105E89A8625B0B7D76E36768D00CCA75F68679938149075AEBCD04EEAF7C5C0BA4032C60B45515CEEBE0B57AD05C25B6A932ECB6F2ABFB088253039908315629AA975B8D2EA5CD5A1964C231934A310BD222C8896CB870A620293536A4D93E29538AE3EBF091B6EDC84083CD43DDE7DB84440C8DE69079EE11A1B7E354D2E02A326E0FA84821DB48C9B886C10D2EF5286F8DA7206C1CA5EA16852579B0201F8598446D4C0E66EE116507FA2E1F853C11890B32D419E093698B6B2EBAB9C4417178985B8DFE2767BD8624A2088E756EFAE37E98490982288094977A7A2538907653FA78952C133017BC129539300159E75E15C93807F8C8ADBC908C466D720E2A86D8BF9ACFC10263620B9B31DF7EEA587891D693154CF251AF35A2DC01B4A837FB8CA74F1AA744CC53AC3953B68D51F71D14CEFDAF91E3972B2993DE80F83C9E1E401128531A8C17462BA7892CB962BC084C91130CE933A36B2A74AEC1C5BFE498684351996E15CDAAA5C5AB9C767228397135845E82469085104AE5D97523C0C7634D0DDB2D7F49CA6822F6BDDD8AAFC7558A405D1BFC00DC8E80506B0B2A12767C0F905CD1F568991CF277EA51205F04354E80255FCE010B93E822CE08C30448C6F631E6D6339949E100F7AF00357E982378611BD2F17190CE6E2E71A43FB8E03ADAA98D54F78445B2C7C433409ED8CB75D2FC9344C7B2C24A5FC368D5D5C7CB9BEECE94647CC6B65C69DF829EA1EC6DA913052A534990D27756B2BC0AF7A533A945A49B4A3B20668D94254326E18226EC1605B63EF618C1259C81BA5342A20CF8DC64E39F4D7929694B516E64CD03614290D3C033B6B1C0B030171CE4EFA65491D1F376FD2469944524666EA0A28AAD35A48E15EE79E677FE2B38E9B11C1062731935F2513AAA3A5230A75A50AA446700B10C5A326776241100A2155BAE98E8CAB6843116B153E8BAF5D46399CF5F6EA67202B0144E34B0E1BAC6C1AA55FE7CA565E643BC1E15A1C09FC378CE37548D23BE3283A74D6B590CC8D25C64464B83A506D27AEC7F721A911F039AC4671A02564DC266A2EB78D6348A8D100E71F99FA861E499CEFBF200F8865D244BF30BA23EA4706290BD108EF82340B134F9E1ECE3C1226714C15BE72D5D33CB8DA2CF49181DED7BA3055E26D05DD8511D5ACA38192BD1AB7271B653005A6EBD06A3DE983CA28259478B4EAD1D66BA479A92F375E892075329BEA241C4897187F64C370F0171DC5D48CA543F3599FC8A87E7AD5C46306DE61518F471AE909B842601348CE0D92D657015772A7A59E957848263E359EB50DE8CCE2D2E37BC6DD42E4B880221F2B949BBE202A2648F42E9CD9E958D5481BFD186D444320FDB69B249C4EC2A11E4C6868D1E27E0F601163384AB8BA30B063B4D82C70CB26F061B2450AFA9C489A2FA3F11A09EA4CEF28B89DB6C5EF2A23CA9CBE1D980314807ADF85F64B3E627917AA0D05E35687BEE745FE67FB72842B1684587A9C41A8CB8D424E30BA1A7D0A17C6891738AD96B07C9881141FAC9B1C76AB47D0A1D32EE8B3D1B7F38003AB07C7CC89270E004942C3EEBA178E62D0F6EDA1C81B97E3EB2492AC594F710F2414119A481936A5E970F941C7BBD08DE49304D1E446CEF76E52713DD9485EA73CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E");
+        byte[] sk = Hex.decode("FFFA0806FCF5F30D09FD0009F707FD010A0004FAFAF504F2F3FBF8FAFFF309FAFF1104FDF8010503F7FE0E08FAFF1806EFFEF2FEFCF1FD06050B07FA060C01FAF31300FDFD00070100F9F5110FF8FE0FF709F4FD0307FEFCF60800FF02F2FF0FF9EE07FF0703F002FD03FDF80902FC0B02F7F1F304010407FF0701FEFCFE15FC0503FE0804FE0704F202EF09FBF408070D07F206F307FFF502FDFD07F70404F5F8FB01F704E7F700F40501F7E00CFE080AFEFBFFFFF806FAF606F5FCFCFBFBFB00FD0AFBFFF5FC0B0EF411F4FEFC07020707F9F817FA0A0507FD0508EDF3FBF805FF00F1090607000508FB10EF0504FFFF0301FD0D09EFFE02FFFEF5FCFEF1FF0403F5F3FDFB0506F703FF09FDF3F9F5F7FFFE08110F0201F60701FAFFFA0A0404FE0809FF0AFEF20D09FF0A10F7FC020409F7F003FFFDFCF1F4F707F801F103FFF504090600F50301FA08FCF4F7F90C1310FCF20AF20E0CFA050103FCFA05FDEC0B08FE0502F8F9FD0C03F6FAFFFE01F8FB02FBEFF8FDFD0005030002F9FEF2000603FFFDF8F4F50206FAF9FC0BFE02FBF20BF400FFF90E0001F10FFC07FC12FBFDFEFB0702F8011008FDFF01FC06FF010501FE060A0201FF0302F116F4F8130008F60A100906F90309F7F605F4090C0510FEFAF8010609000A09020C1203F8FB02FFFC04FF0BF803050AFC06F1030808F6FB070000F80D0404FDEFF8F804FF090DF708FA050E02ED04FC0105FAFE080EFA06F706FA01FAF3F80404F905F3FFF0040406030505F808020B0803FF0FFDF8FB00F80102F7FAFDFEF3FAF9EDF0F704F302F10503FAF20302FBF306FAEC05F20A0400FE08F7F90506FBFEFCFFFA0E14080EF4041002F60702FAFCFD0F0B05F7FF080810FF030807FCF5FE020203FDFBFE021209FA05090205F800FB0011FC09FBFA14EC0610040C070A020C0CFE0006F900FFF8FBFE00FDFF0305F50B01FF02FCFE0708FDF7020006000011F800FEFBF7F6F6FBF4F50DFB09030D00060C030200F7FEF3F8070605FEF7FBE907F903010003FEF4F9F50FFBF0FD010810040502010AFDFCF00A0403FB030F0203F6FEFF060CFC010905040BEE0EFE01FFF6F6F508F90C0503050404FB090800FFFF110D0B0111FF0BF405FDF302FBFDFB00F603FD07F4F90102F5FF0908FBFEF901EF050DFB04010603120F05060B06FDF1FAFC0408FA07000E0DFAFDFD0AFCFCF9FBFA0AF201F904F9FAFAF80CF40003F6F9F8020805F709FD08F90BFCF7EFFE050AF901F4F9F4010704FEFA0CFF0506F103FA10FF09090109F90602F7EC03FE0107FF02FCF101FE05F9EEFDFAFC010301FE0202F80304050107F30107EF060100F9EEFCF60603FD02070CFDFCFE0AFDF90EFD03FF00FBF70308F50407F8090D010AEC0603030909FC020908FCFDF8140CFC1510F404FBF20AFCFE0FFB03FCFB0105F8070B00040AFAF6FFF8FB0C09FAFBF806000109FEFE0F050908FEFA02FF06FB03FF01EFF40AF9F502FE030DF4F6F9F4FCFCFBF9FC05FFF90A0204010901F903070C1007F70008EF03FF0DF705FCF8000F0A0312F204F810030D00F0F7F5050008FFF6F9070AF70600FCFDFE00FB03FDFCFC0302050506FDFE04FAFEFC0303F7EF05F3F70BFC06FE00F3FCF3F5F3000703FB07120604FFF9040A0406FBFAFAFB0309040C06000312F901F30200FA00F3F4FE030505FFFEFAF7030D1104FCFC02FCEEF116FD0B010B0EFDFBF904F3FAF3FDF90001030801FDFBF807030706F70304FF16FDF700FD010BFE090DFB0502FF0CFC00F5F00A09FBF604FF02050A04030AF3F505FAFCF809F900FDF00A06EC04F9FB0302FE02F6F60B0707F8F209FCEDFE08FBF709FC0805F904FDFE020AF4F2FD030303FFF8FAFB0CF60BFFFF07FC090613020502F7F2FD02FEF2F2F400020FFCFBF905F4FF03F706FAEEFA03FDFAF607030207F8040FF71101FEFFF50200F3FF02FBF901F50A0B0706FC050900FA000AFFF0F306F802FDF00BF910F202FEFC03070501EDFEFF06F805F7FE04ECE5FC0C00F50B0703000AFAF0FBFEF5070FFBF3F10600040AF3070704FA02FEF80703040107FDFD00100B04FF0402FD0419FDFBF5020901020B060BFEF5FCF207FD01F2F609FCFEFE14FC0D100900FAFBFD0AF802F80B01FD0008FDFB00FC0102F7E9F304FB04FEFCF60AFF0B07FB06FF0D020700EBFF040B00F5F8F0F905FD040807F3FE04FAF2F60AFAFFE70AF7FBF6030D0B01FC05F701FD0BFEFEFE02F70B0101F4FB08F1F600FC09F5FE03FBFD06FF0405FA03FEFCF7F601FD01060800040FF8F4F9FB070EFCF503FDF707F0EE0B05FFFC0703000201FBF9FB03F7FEFB06FFF200FB0004F704FB0904FFFE000603EE0A060BFF0AFBFF08F6FEFAF9F6FFFEFF0BFA0AFBFA08FFFFFDFF010BF60512F9FA01FDEB02FCF9050DFE01EDF5F3FA03041304FBFCFC01010CEB06011304FCFC04F7020BF6F10400FBF6F9FFF60FFEFFFBFF03FFFD03F108FFFCEDF501F5F8FA060305010501FCFE0DFD0600EF0304F907FF0000FB03FEFCFEFF0FFEF8000402FBFCF80D0709040B15FBFDF7050207FF01F8FFF7FA0608F8EFFB030AFC0DFA0DF8FC07F603FF07F408FD050EFBF9F5F5F60403F706FF030C1306FA0DFFFFFD090405020105F7020500FE07F701F608FEFD09F910ED0BF6FCFAEF0B01F4ED0708FB000213020700FAFBFFF9030AF6060A01070712FC0C03FCFFFC060407080104FFFC0904FCFAEFFE03F8FCFB01FBF0FE05FD06FD0F09FDFF03F40504FF00FEFBFDFD0EFBFFFE0B0AF4070C00F4FFF5FA05F10505FEEF00FD0303040103FC050407FC05F90104FD03F501FE07FD0BFBFE07FCF5F805020301000103060902041101FCF8FC00F301F503FD05020601FEFE070409090400030BF60B01F60DEB00FD1803FE0103F70AFFFBF90408F611F7FC0707FE080203F305F8FDFD04FAF5061005070301F607FAFFFD08020AFC02070BEE0907F9FB07FE02050F0AFE0FFCFE07F509FB0C01100306040E02070B0202FC010DFC09F801050102000CF5FDF9FFFB0004FDFB0A040506FAF00607FD0EF10DF5F8FB0408FEF3F4FE12FFF500FA02FFFCEFFDFDFF06FC09FF07051600F7050702F60608F102FBF60CF50400FB010401FE0A00FE0BF50CF809FEFF04EBFE11060501FEFC040B040605FF00000100FD0EFE00FE03F70DF4FAFF0400080AF80310F508EEFCF20AFA030C03000E0308050600F901EAFA0A05F800FAF6F808F803FAFEFF01FA010902F9FDF6F60A0CFEF702F408FAFEFD00F8F3FC05F9F60200FB05FDFEF50A01010D04F908E600050CF407FF0404F5FDFB06FFFEFC0006F604FFF707F809F900090E01FC0C040AFD0C0BFF05040AF30003090EFBFDF602F8F8FD0101060513FBF805FC0B00070AF500F803FDF50702F0FDF9FCFE01FD05F8010E04060000FAFC06FAFB16F6FBFFFF12090805F906FBF9FDF8FFFF1106FEF7100707FC0406FA11FE01FDF5110D0B0DFE01F807F6FF04F4EC00F50502FCE90AF9FC00FEFB150D0008F8F4140F010CFB00FB040F020207F209000BF8F409FFF704F3F7120511F90FF8030904FFFB020900F907F40BF605F607030BFEF301F207F1F801F9F705EEFD0FFE04F004FB0AFB0405FD0000FEF90DFF01FD060606F8F8F60D0AFA01F9FA051C00000B0FF9FE01FC00FD0E04070109FC0507E409F6F601080D02F302FCF702FC0BFEF302FFFF04090105FFF8F6F607FA0A00FFFDF5FC0AF3FAFF02ED09F2FBF7FBFF0CFEFD050D070505F8FBF700FEFEF80005FFFAFEEE0AFE0BFA000906020307F60607F80200FAFE0BF60604FF08F9FBF50606040802FD0004F4F8FCE9130911FE0305FEFEFDF905FFFF0D0B0EFA05000500000406ECFAF8FEF40AFA05F504F9F9010BFA0302FEFDFC07FB02FCF40D04FDFD0803EDFC0F00F7FE06FB090305FCF9FFFA130B0BFBFDF501F7FB03000D02FD0C0300FD0BFFFC13FC04F90E0804FCFBF803FDF7050907FB0702F8F9F8F2050102FC01F40A0614F6FDFDFCF00805FBF803FCF5090EF6FE0900FE04F8FAEE14EC000600FB0801F7FE0408FA0AFE05FDFDF907F5ED0A0C000409FCF305FF03090AFA0401F0FD0A000DFAFFFA02E9FBFE07070501F508FE03F50BF205FC05F50108F405F9FA02F101FAF50B06FC0AEDF4000DFB0D06FCFEFBFFFBF700FD0B07000703E70A0406FA050400080001F00E020D00FDFD0800FEFE04F90300050D05000D090700FC0E03F50304F90007F60D020002FFFE02FE0903010303040706F70800FA0706FEFDF10608FCFEF4F5F1F8F610FBFCF002020702070B0DF3F707FB07FEFB0F06FF0A07000C12EB0FF9FA09F6FDFB0B040AF7110302E9F6FDF0F90A04FF0AF3E6FF12FA010804030AFD1402FF05FA020E040201F502F908F00E030C02F8F9FFFB0201F903F3FFF6000606FBFBFB0204110CFAFCF40202FEECF3FE0003F007FF0AF90302FEF708F00DFD0B0AF906010206130809FB07FBFE0907FD080002F804F2FC0CFBF40301F40607F7FCFAF605FE06FD0DF4FCF60400F4FD0AFCFC0B0C01130807F502FE05F802FFFA0404F908F6FBF802FCFC060505FD00F604FE040802F20100FC070500F40305FE09FDFCF4F903010C010313FBFFFE06F005FB09F8F2F3FF0A05FC0AF6F7F506FA0409F9F900FFF7F307F602EC0CF8060EFB02FEFCF311F910FEF8F0F904090A0BFBFDFF03F8FA0E02F90D07FEFB02F9F206F40308030A00FAF704030204FA05FF0BF600FF0309FC05FEF9E8FEFA05F5F907051405FEF3F5F0FFF5010BFA06F6FD0D0504FEEF000CF7FF04F004050307FF0AFD00FA05FFFD05FC02000E060DFA0005040AFE0A01E1F2FA1508FD0804FFF900030906FCFAEB00FDFCF600010207FD05FB0005FF0206F8F900FEF902F903FC0BFD05F3F9F81306FBFEFFFF02F5F7EDF10FF50D03FFF6FD03FAFC0606F90B020B03FD07F90C0213F609FF07ED10F900F801F6F8FFFD05FD04061005F6090308FCFDF10400F9FE1702F811FDFE05130307FAFF0AFDF5F60907FEF5F9070709EF0009FEF8F8FFFE0A0CF6F30100040807F70D0D07FE0303F7F808F7060808000003F303F1FF01F700F7F70CF8FEFFFF01F70303F50600FFF800FF010AF3F4FAF001F105150C030803EFFDF6FAF3F6F900020C090A08FF0400050606040408030401120AF9F3F505F1020402050E010407FEFCF9FC010008FBFD09F7FCFC04F9F804F604FD0BFB02F407F8FB0702080404F5FE0A040B090305FCFE090C08F90705FEF8FEF8F1F501020DFA0D0504F8F900F9FFFB00010402F6FF0FFAF905EE09FF07FFFA180501F9FBF9FAFD040A0405FD11F00D11FFF900F2FE0406FD0BFDF8040DF2F80801041205FD06EAF8FA10F2FAF6FDF8030CF7F7FFF702E8F510FC070E0706FB04030D0C02F60C0904FC0AF5010700FBF703FAF708F501F7F9F80FFD050E0300F90D03FE1510F0F9F7F7FDFEEC101405020E14EE05F1F50C05FE09F5F602FA04FCFBFD01F003F6FBF6FE01030D05020310F6F8FAFF080906FDF6F6F604EFFA00020704FDF9FDFC07FA07FAFFFE05FFFAFD040C090E18FAF1F40400F3F40103FAF7040BFD1105FB05F7F505071006090BFEF7FBF90FFAF800FCEFFF07FAF8F905080B04F4F9F601F707FD0E01F5FFF3FF0700F4FA04FFFCFDF6FF0A0107FDFFF404F80AFF000402F20A06F90400FF13FFFF08060DF402040511FE05F7EF03FF0206F6FEFB0DFE08FAFA03F3F3FB0CFDF9F804FEFD0D03F9F8F2FDF7F8FD0B0BFAFDFCEDF3F60FF8F807FA01000203F9FF0C0AFF0D00EE07FC02F4FB050B03F608F9F4FC02F5FBF80200E905FBFEFE0BFC050904FEFD060006F7FB01FDFD010FFE04F6FC1207F511050CF8F6F900EBF7030CFA05ED090009030B02FBFC0608FDF60A01F608FC05FAF7F9FE0A040D010B0EFDF90402FBF702FE090FF5F7FD010301FA05FD0206F4FFF4030402FA0BFF1008FC03FE0A0607FCFD0108F60C010B080110110501080506F60708010BFFFA02060D100304FF03F8F503FC09FF08F00304FF01F1FC06010404F8FA0406FBF7000307020DF50C04F9F4FEF500F50105F70AFF100B0F00FC01FC03EB17F9F1F3F5FA080FFC0307FA0900FBFCF1FD0106FB0BFCFF0D0D1806FAF90EF7FDFDFE03FFF7080404FBFCF9FB0AFDFF13FB0502FD00E9F50CFB100800FDFD0FF4FF010307010AFAFC0DF906F4FC0514100B0A03010301F6FA0609010604F90009FA07FC05F7F2F601F3030DFE0A00FAF903F7F603FB05FAF8F9FE080F06F11604F3FCFFFEFE0200FCF8EEFB09030CFD0306F504FCFF070AFE030805080CF405F5F9070416FEF5FA0003F4070203061000FCFC05F9F809F9FFFDF908010BFD0602000D100BF8F8F9FEF6FEFB03FB01F6F70401FAFBFCFFFE0100F800FD0605FDFEF0FB0AFDFC080005FCFCFBFA08F7060205F1F60305020800FE0307FEFB00FA00100CFD060A02F70EFFFD060BFC03FCFA00F9030AFF03FAF7F414FC00FBF605FD03FFF0F802050BFA01F3FDF802FA010DF90107FA05010AF703FF050A0304FFF905F8FE05FDFBFA010302FD0E0800FCFCF8FF0401FEF30102FAF80D09FB0301ECFFF70301F3FC0513FF05F3010F04F70AFFFC060400F1F1F8F802FFF30FFEF8FF01F9F6050402FCFF01FCFBF80703F3100B05FC05FC06FD02FC18020B101102F40604FEF610F005F811FC04FAFBF50E02FFF713EF03F50700F3090301ED02F507FFFDFA0306FA030805FFFD0F0CFF00FAF0FB0609FFFC0C0307FFFD0F03FDF10606FAF900030C061705130C02F60AFF0DFA060304FA0106FDF7FCF404010AEF03F9ED06F7E8F9FDFF070901000105FBFC04FFF8F5FF0306F6F7050700F3FB0608FEF6FDFB0E04F804FEFDEDFC02FE0009FE0108F9F8070506EC1408EFFFFE0DF7F70DFEFDF8F80408F707F6FCF6F10105F80C0210FDF300F3FF0702F9F208FDFD0500FDEFFCFEFE0005000C05FBF90D0308F801F7F9F30302FBFBFC04FC09FB08FA0B00F0F5FD02F90C0506F80904F00B03FBF809FBF7F7FFF6F908060503F5FEFE07FB02F909FF0D0305060601FDFEF8F70007FAF30301F2FCF8EFF1F803F6F8F40D0603080100FDFE09F90002F71002FD01020DFE0105FDF0FF08020403060CEEFE0C06000001FC15FFEEF80FFA04F301FB09F1E90C0401031204F70EF4FE01FFFCF208F8030BFD1405FD05F205FEFA08FF1109ECF8FAF8F904090B0A01CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E4F5C254B6292FB5C3DB9561B8793D8AE3E1611423AC0A9F8CFC13E1C85FEC6B5"
+            + "3E8FE33CAE1CEEC574275C02B17AE78A0018BD4212E087C0901E518796AAA752B6282A7D0DB145AE");
+
+
+        //
+        // Key generation.
+        //
+
+        //
+        // The QTESLASecureRandomFactory.getFixedNoDiscard(...) is used so the same key values can be generated for a given seed
+        // for testing purposes. You MUST use a real SecureRandom instance in reality.
+        //
+        QTESLAKeyGenerationParameters keyGenerationParameters = new QTESLAKeyGenerationParameters(QTESLASecurityCategory.PROVABLY_SECURE_I, QTESLASecureRandomFactory.getFixedNoDiscard(seed, 256));
+        QTESLAKeyPairGenerator keyGen = new QTESLAKeyPairGenerator();
+        keyGen.init(keyGenerationParameters);
+        AsymmetricCipherKeyPair acp = keyGen.generateKeyPair();
+        assertTrue(Arrays.areEqual(publicKey, ((QTESLAPublicKeyParameters)acp.getPublic()).getPublicData()));
+        assertTrue(Arrays.areEqual(sk, ((QTESLAPrivateKeyParameters)acp.getPrivate()).getSecret()));
+
+
         byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
 
-        QTESLAPublicKeyParameters qPub = (QTESLAPublicKeyParameters)kp.getPublic();
-        QTESLAPrivateKeyParameters qPriv = (QTESLAPrivateKeyParameters)kp.getPrivate();
 
+        QTESLAPrivateKeyParameters qPriv = new QTESLAPrivateKeyParameters(QTESLASecurityCategory.PROVABLY_SECURE_I, sk);
         QTESLASigner signer = new QTESLASigner();
 
         signer.init(true, new ParametersWithRandom(qPriv, QTESLASecureRandomFactory.getFixed(seed, 256)));
 
         byte[] sig = signer.generateSignature(msg);
 
-        signer.init(false, qPub);
+        //
+        // The expect sig from the C version is [sig][message] so to validate we need to append the message to our generated signature.
+        //
+        byte[] expectedSig = Hex.decode("77CA5E0F6AB8586FAE62A68BD7A97DDA9435AF9335EF76BEF113A56D64420A8FDC2669E234AAD20193D2085F9E4B9DFFADA03E33DB4119FABBBCD91B19530793E0BAB71ED5CD61A607055E93FB56F66E8EBEBC6B63020E75579ACF8E2F16E5F6179AFD9E7B20DF0C010C1554791E6D41C224639B9D070AB04F6914E74644B14AEE4C7731675DB2B94237FFF00BFDAD7A070D73BD1DBACEC15094D091A0563F6ECC749FA57AFC4BF8B3009836DC5ED692DEC837953D776076F1D9A0AE98D812758A08F184751D33D29EEFB8868B97E21E115C568BEA8521B60E7D9ECD30CA5DD48EE82FEE169A2A3A1F9EC1366203FF1B1732C1598906F6715B88576EDA2C29FE0D0B4006B03E1B21171E923B5C3775ABBCCD2313186793CA98BA96B932D2561DD915E4B7BBB08B6946C37D71A4077CE9E3868148C33377068A70F1B21C13CDBBBF200F39F52E7A4C2B094F8EFE208CACDA1F7789A4FC471574ABA28BF06CB8940207E06A5B2A0B3CFD2BCBF6421A85D32F5831084A78C914FEDA6D7F1D6977397202CD24A4260B90C11077CA337F10A852A34FBAB455F2B7530E336AF3F2C6AE04B652D7E05FC02264E5C81E0E09FACD01A70F4F66F368D7F308284988BC3E6B4C026F97A65C0B31C1187CD05409C5BA4C22317C3A1CE08EAC8FC25A4D3644B610481E07FE372D19FA622365394E37A6BB61EE38E309736A807A2DD85AC76F88451354FB12AA778E3F05EC69D83AC8EED551070DA0ADC0222E7839D79E2847037A2DB1B2E0F15C376451CE38A11E57896F67D804DA2A4F84BA51308ED69F161A9E08207DA401D9F5EAABDC4F50B4D96357929126A208B0DB29D03CD71A2F203309E41DA4661162A48944F463AA642D67AE9C36FCDCA4F14AF4B136A943B74AD5B8CE6ED6BFDC3755B1750B901584B2C1614A2634D0DAD3F2D54BBE3BFBEE7C07E4D7AE64430364A5A0EE4E4A0D436D569B2DD6C8A121FB33329FD25E2FE7297E939736721E6A2BC37398DD16C7E2CA087182C5958A4D9A21EA3E2BA3754B630BDB7F146F090649ED7A4B1ABB64D58BF1CC54E3FCC8358EA6AF1B2C7622B633AEE12C6664A51F1437368E8D9EC67AB6C2939749A671465EE7A77F9368E145047072664084EAE6233F91010DD5148E2F7A66F69BC5849F80186CE26C0D927BCA38E5BB8A28302946A43CE57872FE5D735793337127E41EAAD931AD96FED22862FC0AEA72569E9B56596B8BC36A186D4D03C87D484111F18A6187CD7522EC926153488DB161D8ED5CB05F144C01ED77C7614FB42FCE566898BC9F5AA9FEF0F7093818A390A22EBA9404EBC3A79FB3543640DF29D2B5B9A601E916DB36CFD66A5067E7FA379A640D12B99D75B67F8ED059B761D3FBBDAC235024D071EAB9A2C6F13D5C6DAE1BF952EEA902A5A25F1FCFFCF8B0ABAFF0B242B48EBFFA6CBA9CE3654673F3AD4CD5E2ADD2F3E630B7FD48B1F7578CEE7D1ADC54360952ED1F1951F3F4ED531BA57BADDBFB7EC437E42A429DEC044EDBA509E5C18BCF8DE6118D6935EE38B3F94C12300D49E9F434F227407F3C7FA1ECED88D08DF04B4A518C508A3B93A3A8C60ADBB8C0B5F3923213CA531007E0CAA77E5361CBBA1CA6E49E81BA87ABE3C07E9E4590635486B44212298CBF9EC48920C0A899CF257421B734377B18141550B3954E23EEF90D6E20FEF7BF04C17DFE8083C0116786DC771DD802A69B2516396A45F6C755E05F228B4C31835F8E46591C80CD10250470B4421FD5C86B97B48C35CFE22721C3A9BA3DB8BCC739AC0CE5D2BE05B8569C0E3AB51CD112E4BF0137D6902E08E5636861E00EF3D58F3B05C63A5ED8286EE075B46EFBF2CE806301C059602FFEACD4CA5E25FC72424D4A982FE4AA6A6B7821AEEA4A4C209E78034CD69E5AEB14D57E5685E0F90BCA8507FF339CC084889541BACDC155FF24CEFBB79FB2496224A06517CD7A5B3B0C829A6ADE5FFDF70BF197E1927D080C20EFADA5BF44764D7062609F62B8C878C9A66063C957837DEF9065E571E12F44AE02E0A023F1DF37E0F5AE70C5F2699654450C112AE96927D2E46AD1F2116885D40D0373BF5B9AC1B07D99B3EBB739EE4B9F9E369F83F5806AE6A528E8E373B512A83E2DE089F3C52C155C282A78D3D8F1BD9EDA5399D48476DE5F2074F4122504E003072BABEC347678E91B921D5B583DB4B4030BDC547E948CDFA07019B180823AF3C88AFB4954F239662614826C26E3D68B12270D0426418D0DD2815F8AA43E6DBC95CAD2B55E0F5438AD46806B3CD319430550317B7C80BC8DECCBCBC651C6AA00EAC9E514FB92188F49E48C74CF2A595D42BDEC4AC6A5229179C16760885168B18D5C5F86CD500BED77F5D4041C4AA244A1679682E9A62BBD5303A9811DA40AB7D5233B13F19A6C30F700C7D5C670CE4C14B7F7CBB6BD593029B4E55D4FC010171FAE640FF6D6FDDDFEA1E1A197B41369580A38FA6979EBA701F4BB00D8F6D4B24E180A532FB235F12003EBA74BBB329D68488BE399EC1066F768204388CAB27D2766E1E5502E3F690DEE57273951BB799F237FC0F1CE7AC404F64DF1238F352D70658755184E027D0BAD33616A821E1C8CA4F281D14A531D34113AD17D971A3D5E6B022725D395AD3F594277690C96877CD96FD1761E226E5D3A98F16F33E590EEF6CFDDF7A9E5D2A2288F4B5195BF3CF923229D617D4D594D5A76BB74E2A5558DEB44CDBAEF71ADDC3A4AF7D1570347FE0F7C715656061D0B8967D5B8147D13AF1A515C1E07EDE157BEBF8F73FD0AAF94B7F3695BADC3D5D81454BA9A19DED20969CB1EA356DAEA186D25EE563CED503EE3B0F4ACC8B59C2071F4D7454ED60083BEB7F33DD9E4975E85AA49A17ADD101DA612EC0D74CFF4B1E6616A8B7A7F7771AB0BFC956152F4679F3864BA08000F232F62F33D3328443505B5CB6F15EF4CDB365B1CBDF0D750DB39F49EF9BF31484CB79CA5F5AD334E7CAFFE7D7AFEC3E821DFEF387AA0677085941522EBF93D2414E9C1365B534555895B635F731E44480556ECD61DF5DD27794209066C7BB7814170E12C6E3EE938B3FF0984F1BACBCAB09DA34D015721B55E156A9276E2AB76C9B80886CA04B825BD140D1AC4C6CE873A4D38AD274F007E989FCA8BC3D05B19475B414A9C093ECA93BBBB45F8A4DD0360807ECFAE3A7DFD1B0AE736B7C17755A404E5FF6DB6B9A61B16A66A08BCE6D6C6AB31917A9EAA5E4715387A634F232CF90CC9AA9E1609F0E567516567EE1FDDC535FC5E5628E811B43C9993FCA6E4241913C2665E4E7A09AF46FF90659CDF0D022A30D253FCCDA2EE8F18714E1869DA71730AF568161AA0417E7A7B338D94BD85F5D6C30A5D0E6C38F5EFAAEC68D7D383D2529F7F696A3A061BE6FAB1EF8F2D7F91C6F10A7898C87BE2679F0CB3EFE5883AE4D954C5913FDED07FDF70A8AE16B2D11E8B0D8A73904388071E49997FF18766592B59EC2443B3C7C5B465508A40559D764CD499D8574992FC571F483B0A3BC8B28BEC0D042585435CA275875E0ED0E9E00BF401D0071769EF7345FF3F7A87DD10204D154422C7F182754DC9AA3CB764EA920329199FDEF60D8D85F5ABA944050A3A4824EC5032B67FFC7C56A591B2A2C3843617AF31B1C1C75E029093BBEC7374C98E79C21A0EFF8DBD15298173D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        byte[] generatedSig = Arrays.concatenate(sig, msg);
 
-        assertTrue(signer.verifySignature(msg, sig));
+        assertTrue(Arrays.areEqual(expectedSig, generatedSig));
+
+
+        //
+        // Round trip against the public key generated by the C version.
+        //
+
+        QTESLAPublicKeyParameters qPub = new QTESLAPublicKeyParameters(QTESLASecurityCategory.PROVABLY_SECURE_I, publicKey);
+
+        QTESLASigner verifier = new QTESLASigner();
+
+        verifier.init(false, qPub);
+
+        assertTrue(verifier.verifySignature(msg, sig));
+        assertFalse(verifier.verifySignature(Arrays.append(msg, (byte)0x00), sig));
+        assertFalse(verifier.verifySignature(msg, Arrays.append(sig, (byte)0x00)));
     }
 
-    public void testGenerateKeyPairSigningVerifyingI()
+    public void testSignQ3p()
+        throws Exception
     {
-        QTESLAKeyPairGenerator kpGen = new QTESLAKeyPairGenerator();
+        //
+        // Values put in file because vectors exceeded length of string constant.
+        //
+        BufferedReader bin = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/org/bouncycastle/pqc/crypto/test/q3pIII.txt")));
 
-        kpGen.init(new QTESLAKeyGenerationParameters(QTESLASecurityCategory.HEURISTIC_I, secureRandom));
+        byte[] seed = Hex.decode(bin.readLine());
+        byte[] msg = Hex.decode(bin.readLine());
+        byte[] publicKey = Hex.decode(bin.readLine());
+        byte[] sk = Hex.decode(bin.readLine());
+        byte[] sm = Hex.decode(bin.readLine());
 
-        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
+        bin.close();
 
-        doTestSig(kp);
-    }
 
-    public void testGenerateKeyPairSigningVerifyingIIISize()
-    {
-        QTESLAKeyPairGenerator kpGen = new QTESLAKeyPairGenerator();
+        //
+        // Key generation.
+        //
 
-        kpGen.init(new QTESLAKeyGenerationParameters(QTESLASecurityCategory.HEURISTIC_III_SIZE, secureRandom));
+        //
+        // The QTESLASecureRandomFactory.getFixedNoDiscard(...) is used so the same key values can be generated for a given seed
+        // for testing purposes. You MUST use a real SecureRandom instance in reality.
+        //
+        QTESLAKeyGenerationParameters keyGenerationParameters = new QTESLAKeyGenerationParameters(QTESLASecurityCategory.PROVABLY_SECURE_III, QTESLASecureRandomFactory.getFixedNoDiscard(seed, 256));
+        QTESLAKeyPairGenerator keyGen = new QTESLAKeyPairGenerator();
+        keyGen.init(keyGenerationParameters);
+        AsymmetricCipherKeyPair acp = keyGen.generateKeyPair();
+        assertTrue(Arrays.areEqual(publicKey, ((QTESLAPublicKeyParameters)acp.getPublic()).getPublicData()));
+        assertTrue(Arrays.areEqual(sk, ((QTESLAPrivateKeyParameters)acp.getPrivate()).getSecret()));
 
-        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
 
-        doTestSig(kp);
-    }
-
-    public void testGenerateKeyPairSigningVerifyingIIISpeed()
-    {
-        QTESLAKeyPairGenerator kpGen = new QTESLAKeyPairGenerator();
-
-        kpGen.init(new QTESLAKeyGenerationParameters(QTESLASecurityCategory.HEURISTIC_III_SPEED, secureRandom));
-
-        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
-
-        doTestSig(kp);
-    }
-
-    public void testGenerateKeyPairSigningVerifyingPI()
-    {
-        QTESLAKeyPairGenerator kpGen = new QTESLAKeyPairGenerator();
-
-        kpGen.init(new QTESLAKeyGenerationParameters(QTESLASecurityCategory.PROVABLY_SECURE_I, secureRandom));
-
-        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
-
-        doTestSig(kp);
-    }
-
-    public void testGenerateKeyPairSigningVerifyingPIII()
-    {
-        QTESLAKeyPairGenerator kpGen = new QTESLAKeyPairGenerator();
-
-        kpGen.init(new QTESLAKeyGenerationParameters(QTESLASecurityCategory.PROVABLY_SECURE_III, secureRandom));
-
-        AsymmetricCipherKeyPair kp = kpGen.generateKeyPair();
-        
-        doTestSig(kp);
-    }
-
-    private void doTestKAT(int securityCategory, byte[] pubKey, byte[] privKey, byte[] seed, byte[] msg, byte[] expected)
-    {
-        QTESLAPublicKeyParameters qPub = new QTESLAPublicKeyParameters(securityCategory, pubKey);
-        QTESLAPrivateKeyParameters qPriv = new QTESLAPrivateKeyParameters(securityCategory, privKey);
-
+        QTESLAPrivateKeyParameters qPriv = new QTESLAPrivateKeyParameters(QTESLASecurityCategory.PROVABLY_SECURE_III, sk);
         QTESLASigner signer = new QTESLASigner();
 
-        signer.init(true, new ParametersWithRandom(qPriv, QTESLASecureRandomFactory.getFixed(seed,256)));
+        signer.init(true, new ParametersWithRandom(qPriv, QTESLASecureRandomFactory.getFixed(seed, 256)));
 
         byte[] sig = signer.generateSignature(msg);
 
-        assertTrue(Arrays.areEqual(expected, org.bouncycastle.util.Arrays.concatenate(sig, msg)));
 
-        signer.init(false, qPub);
+        //
+        // The expected sig from the C version is [sig][message] so to validate we need to append the message to our generated signature.
+        //
+        byte[] expectedSig = sm;
+        byte[] generatedSig = Arrays.concatenate(sig, msg);
 
-        assertTrue(signer.verifySignature(msg, sig));
+        assertTrue(Arrays.areEqual(expectedSig, generatedSig));
+
+
+        //
+        // Round trip against the public key generated by the C version.
+        //
+
+        QTESLAPublicKeyParameters qPub = new QTESLAPublicKeyParameters(QTESLASecurityCategory.PROVABLY_SECURE_III, publicKey);
+
+        QTESLASigner verifier = new QTESLASigner();
+
+        verifier.init(false, qPub);
+
+        assertTrue(verifier.verifySignature(msg, sig));
+        assertFalse(verifier.verifySignature(Arrays.append(msg, (byte)0x00), sig));
+        assertFalse(verifier.verifySignature(msg, Arrays.append(sig, (byte)0x00)));
     }
 
-    /**
-     * # qTesla-I
-     */
-    public void testCatIVector0()
+    public void testKATVectors()
+        throws Exception
     {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
+        String[] files = new String[]{
+            "/org/bouncycastle/pqc/crypto/test/qTeslaR2/KAT/ref/PQCsignKAT_qTesla-p-I.rsp",
+            "/org/bouncycastle/pqc/crypto/test/qTeslaR2/KAT/ref/PQCsignKAT_qTesla-p-III.rsp",
+        };
 
-        byte[] publicKey = Hex.decode("D2F21F7B398701A369A10AB5CA5752324D01D2E4E85D7ADE9C21F2F41CAFDF25B15F173504C315EA250E1EFC243557E90C23509E7D2AE486518448EF18BA837DDE8DD6A30271E793D50DC7999485AE30A649636257E71DE6D65C9803A8FBB384181C6C28604C2B201C938A5198C01704B0F56ACEFD0CF58BC98F81E20CD233C6B5523C8A00F5DB1C934D2B4FB3609C0CB37543A42C4145CAA283C269B49C8EF323CE941F5FCEDB172CA9B5DD116DD6B2B6284DE55C2CC033426C84BD91D74837C6140E12D0C6B05765FD269BD23200FBE110D61856F8C5CE55FDF7269D6BE7D9FC213C885D74F2311A46BD5E32C06308C7C2E7F26BDDBDD10DD1F6DA004B8D28990D9A62276C4FFA90BF6D734DEAF116000CE004EAA0BB25640F9342E9C2FD40774DE360FA1560D478985BE9639F1E5BDD5B3000B364E12AEF3AAFECA99E4DD24530BB223663F095E3254E42A9E0D88639A946388331A4123CCB71A4D8925DF3EF6B6EADD257788B92E8C5E6596AEB77E0FBB86BCD03D0EE68D9B5C08E0E4B67273CCAA8544871276C9375AA0D0CE5681EBA576EF02A617879E2C27E137C8EC106B544D727CEC5B93F67CD4CB2016640B8FCE631243EB08FAD4F1237F3039376061B1AC3FE724C939CD00000EFC693C6D94828428E4CF1103B6BA8139724322ACC8832119F16528056E7D8CCCCFFE683E91AD84BBDC4D492E0215C45B5B75D1C01114DF952C731DB41D3673D80C21E901D06E898E4E6CC2CF3686ADB6760DBD0076ACD31F610C16B291EA1FB595B912ECC5F3E57CDE588CC14046D1D5AF625B0D52D61075082EF052F5F00B66C429E2B2977665E8719F4E4BE4D0EDFF5E6A793DEE09AC0589A6FECF7732684226E03A9520A472DE129A7EE74956FBA661610804F22C235BF5EA9B83D05C634215473309AC5E0BBCC30929AF45F6669340216DD0DCA2E04FA1697546EC1C93BE569068ADC39790435DB82DB3F3C21F16015CF17D1BAB83DACE254A37BC6E4871ED6031B9BA44EF7A71C1E5A709255A9CD226C0DE6733F1615403820C4B3DB50AAB082C04D0D922550DF4A940C9F20710D7B8208E0E97648F02B330EF436F436E011338523718D9DCFA2C6459DB1920A38E978D57D67D8D163313093C58E5A55F96D0DD7A170F3F255CEC4CCAAFA5044D1B21B2B5D44EA76CFC8F5B1A03A99D5DFD04A0ACE897CF480A008819FDBB7216313829CD14A9A1C9596F95ABB48D6F88AFB2B852F2E2CE687B0623D52A81001589EC05D7F6E582B79F2D036030BC6D1573618A83860A8A77C144D7B2DB988F84B16AF61E0931C27478C99B9A1C15801D0A01464DA0611318334238B2745653F14690E37699BAF5A7576FE451F5EAD46DEF2694711E018E37B24EDA8EBB9364553C5EB976DF38D9E4D21C2D174153ACBB0E2644C9C1EC56C3DB51514AED57AE8653C8361262CC21EFD6CF410160F8070753C59439465E62EAF8A34C17E8853C9817F327E273C2D2911C77C1BF9090D30C4243A39F9865545A83D089C5CB23880450E18A4B0036531164B004072E2EDCDF1A015323256BADBE8C1548F2A4CC00241B18825187015D322CE160BA48C4FAF070969A6F6CA9495E6A1959F0923394E7E5E4820B7C6358C61F4471128C67ACA4900728452BD64F6598DB9A421C8D4D33CA8C077C468DDF8FE8F387E950ED979EC2A1AF8FC473529EA069C21A9F26AA5811D343B0E3373183DB3CA9F10E63BA3FC7F81F4999BD0FBD0CA5D6C5546E9B7ABE2AC4D7A5C0FDC8C33A257A8F09281DE4C38D1273B5B37C88F8611CACD58C4E7F9BB916169104CCFA6FB7087FDA4150D5B84F4837C0DB5C6DB321FC7DC79FD90F707456330B37E0109F0ADE25433D3637112E2D96901E22C734407BD988AD203CA805AE8F757EEA09F327DD49710B471CFC197724948A011E597F3A4564CE00FF701B9B240347F227F2BE01582E07680AC993699D2E1536A155B96AE4E461E3D019F0350CBC52EC12186069382FDBD19CE7D70734FD72F8E61361D6BF9EEDEFEA6D44B6B50E1612ADA4E42033329B098318DD9CF695A2921A332204044994F244C0944993B08009265B8004398CF119F95FCC217D38228F1D1F14BCFC5B7160986C339");
-        byte[] secretKey = Hex.decode("2074F000FEEE3B804206EEEB4FFE07FBEF2FC006FB839F4101076400BFFA09AC6F00FE2228C080FE0944CF7FFEE16BD0FEF80D20A0BFF912102080FBEDF7BF0000FE5F9FBD05CCBF2F3FFFDEAB3F00001F48E000032A648F8104F5EBBFBFFFF92FB0400DF78380C1FAF53FD07FFEDC27C03D00D3FB5F8002F153C080030FD88FFEFA139C5F410CFF5FF000081F38D07D01E137A08002120050FF08D4EF1F42010BBC6FFFFE0EE4AF4103D23F0042FE061C60C2F8F5BF8FFDF9111C604200270010BDF8025CB04107F387C07F06EC0770C201FC53003FF60B5070C1011BE0BF800518485000F9F8FF9F81061E6410BCFCE42F60BFFAF4DF5F80FA020450C0EEB0DBFFBDF9DFDBFF8100F42F00FF091068A04101F2DFCF41F90B28B07FF10FCC2F80FFE73BD03FF9F4F35F3F07FF6BC0C2031AECCF7D04F467E03FF2EDDBDFFF0C02804F3F0608D02F3F060C28407DFD2F54AFFFF905D02FBEFCFB8340FE040C885F8103EA1FA001F8F2F36FBCFF1FDC2F42012D1020C0FEF8DBAFC1061E2CB00103F7CF20BE032E5810420417AC1FBEF3F537F0FC05F807603E02FDFFBF7DF2EA13A07FFDF84F40FE070908A0C1FBE523A0810A04703080FEFEDFFF3D01FB4730020A033440BE0AF86F8F42F6E99BA0C1FFF5BF1001F9F15FC0FEFE06E4AF81FDFA4F90FEFC27B02FBFF702DCEF00F7FB83807F04F0CB3F80FDE09F8F3F01E553A0C006016CA0FFF438983F3E05F5D7DF3FF71A10F03E030BBC8F7FFB19C84F81FCFF03D0BEF9EEDBF0BE02E9C71F3E05201830FF03EC17C0800028741F0000D97B60C004F34F60FE0206788042FD04CCDF00081A8CAF800401708F7F10EE0B70C1F411D0AF00011CDCEF3EFA1654C081FCDC7F907FFB0104508208FDAFBFC0F815B0307FFED417B0C0FFF0471081FFD8ABFF8205134C90FD03EC4FA07FFF1434F08003FE9B507F01DD932F3FF6DC833F81FAF6EBCFC1FC0274F07C0201F4DF3EFB203850FEFF0BE4EF4008F71B20FF04E3FBDF7E030A608FC0FF1904E0BFF5FC0B50BFFF18AC3F3FFA08C8BFFF04EED31F41FD0B78CFBE030B38507F0302DC4FBE00E85B300000ECD78F410105C02FFFFAF393CF8102EF278040F20A288080FF0FD03F7FFCFCF78F8009104070FEFDDF2F80FFFD1DE4CFC0FEEE57D03EFA06E48F400103E89F010228DCFF7F07FC9F3F41FEF4235040F309A4E03F041D980FC1FFF85BD07E00C88BDF0008F5EB7FFEF7DFDB9FBFF7F43B307EF60B688042052B982F83040AE89FBEF2E51BF0C00621CC0FC40817BCF0FD00F3D3EF00FBE75B2001F8170C3041FBFEBFBFFEFDE487C0FEFD318C3FC0FEEE6B8F80FAF4834000F8FDF37FC0051A0C00C200FCB78F0000F2DB1F42F812649F3FF9266CD0C1FC0B8CB0C2F8E5B7CFC0F6062440FE0304F8EF4009145CE03FF5F73350BFF80C9480BEFAEAFB2F02FEF9CF6040FC0E6C403F09016040BE0109A0403F01FFDFCFFE072C48CF00F8FFBFCFBFF80AA04F0103E6A3FF40F706ACEFC0032AE09FBF0C0948CF3E012914303EFD1518904106C917907EF9F56BC000FF1650D0C0FF008CE07F050F28913D0126B44F4205FAC3AFC0F40134D07D0CE93BB03E04EC735FC202035C1F40FE0810A08103E8DF0FBDFD22D8AFFE080A5050C10007186000FE06DC5FBDFA0DC8EF020300EC6F3F071248B080FEF9D71F800B0D3C208008F9975F41F902BCAF01FDF06F9000FC17A0B0C1FA17A42F41F8FABBB03D090108707FFCF5BF2FC107ECB390FE05E25FD03DFF11706040FDEFE34F80F62F30E0FF060E3060FFFC993B08009265B8004398CF119F95FCC217D38228F1D1F14BCFC5B7160986C339F23EB15423271EF1CF476289657DBBB1460665D3944B78BEE92D15AA609768F9");
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] sm = Hex.decode("AF8718AB747CEFA75F4A5F26B5392113BDE8CAF1DDDF1D45B473CB64DFFC255B2E211588DAB5D5D2C5407EBBB684E7D8F0C6AB8EB3AB874CEEB87C9CE32568D0ADA6A3B9716EF9AB92BB9F02424D91DD5C3768E4598D2DA954D0664EA3D61070FA4304983B44D6098FB74B55FE40F156F07D0E39F11DBDE5E0549854ACBD65B2F6EFDBF3CEFB27DB44FA18EAF9BDA9F4409FE020B8FB0C7E9A1C17F5A1D59D6996FC1EC43AE246AAE1A8FC777EF5912EEC95A6F9A2B32A91C2EEBD733E0EAA43C3A33E989D1DEF0B1F412C6299628DF5AEEB599391C411942873FDCE0FA701C58FD2FB383A7FEF455AE12F415496BB0F6E75441BB0F670910F207CFC372B823A099EA84105E1B316CA33587FE990017BA0C52C1EA85C9E0F030B63168B093EE83D81C47EE70D5EEE3F5603E00C3FD306DF8D58C594B4E2047397FAE4BB83FCAC0D3A8BF7896770017860E9612BC4FE5E416AD961C277836C89E29B22C9C6A1F2F8A55FD84D57F4C3550EC14E9B30BFD18A3E479217DB48B65B9E3ACF731C08F8655D2D2F689EB6547C5241887CF017FF9F0EFC49D3448D623353C6846F2C547090873903DC281D2D327C1DD8B49BF87CDF28DDAB0BE1D48B8874D8693DACFEE07E51D5137B57F58D1EDFA374C5FC2A9C42FCA63393C8F24160BF54110D8721BADFA288F304E345FBDF179AC43FE45DFE55D98683A8B7E0A7B48BA4193B01294D6666688EA4099E842F78402874FB1CE5A52FB14B6C6BA3343941E0012556C1BC8184DB46B1E49161E5504815ADB45987E7EABC1C5657D5D8F23847B728F5AD51290995BB742EFB99528147D423FB56EE1118E4C170523E4CE2734219902FB54457BDC111F54497002C0D06D4EB11FED96683DD3F5E1A938F4D141DB638BEC5C8A5BBD5E7F4BDCFB8D03C4D003BC0DEA33119015FC6D2468C0A5635049A04055B542463B4AC17FF4AEB339E674A1E5EEE7C0FACB7E6CE7D2B990C4E5EAA29030F5CB76BD83CB59D215875439F68BD8731A4D5F54B1D27B905AC42D381AC8A1C6BF1F785CCAC2CCA0D08411A2845A920DBC51C82105205F1772DBB0914030BF06115CAC3596E0BE6F97ABBEEB6FC10A84819C84507348F2C887366A82B6FA5DB6B34111066DA4A41A709AA7EE8622A3B3AD5C854381440D08C49BBAA025E7F1D459A2606368285B12E50330DA0FDFB9B50D34CC3664B9F13A4B9E0E65F7F5390A4D8D25353CA2DDC5BFF4A1238321AFE33BBA3318D5984AE57799ACBAB7B5A0C52120CA6014B89B3BEE993183703BC717871A1D080E57ED5E4FB5C64AA4620B5F78DD4144C1005ED3D4D2F1E94FF01DBDF0107B52D5FD3B212A72AAE32BD32846E0B31AEB2064A30361E8F58A82937AAE4C3E74A61C9953406B946809B11379411C910A9E9B7E2384D0E9C09A78763CB2DFF356941B2D863C35FCF0D5735B478D62D1D90DC21E833C871A77E533EACDB08081105BA3B65E8953EFECB9C2096DEEEEDACAD630133A6D35AAADF07019EEBE7D1C9EA1CD25F55454D861C1A4A17BDE58FAEE5696FA4DD496A4C0AE443524BB33B98D8F254ACB9CCB852CA4561BB4E3830A24E5EDB9233A20B37F42B323DFA6DC1A891F0C306A121EA94AD8071D107C4B4CDB294FD524014339CBB821F60CE298EABABFD3D64F6F5237DBC14BA7867740F047325691DEC988E401E8CF1427CD040AFA8A33B8BFFC2AC080ECFD7BD4F22646CC7013210B80A99B1F7D6031FE4F50768531C604E7AE9349E87A0006566B61EFE18DF34C385B1B5E8D35946D8E53A75A7412DEC7E8A28A06E8DA2BA3B860AF01455359C01EEDBFB96D6881E5EEAEA7E1B2663A18410218B1588DE897E8E01DFCD72D08757FE1CAEAEDF71F7303CB91E91EE7ED7C7BC3BA6F72A3C8382FC3ED355C6F56CDC8630B2C3A06EA9DF197823448E70C01F349FB0E6D69958AF7347D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        for (int f = 0; f != files.length; f++)
+        {
+            String file = files[f];
 
-        doTestKAT(QTESLASecurityCategory.HEURISTIC_I, publicKey, secretKey, seed, msg, sm);
+            List<QTeslaKatVector> vectors =
+                new QTeslaKatParser(file, QTESLATest.class.getResourceAsStream(file))
+                    .parse("count");
+
+            TestCase.assertEquals(100, vectors.size());
+
+            int type;
+
+            if (file.endsWith("qTesla-p-I.rsp"))
+            {
+                type = QTESLASecurityCategory.PROVABLY_SECURE_I;
+            }
+            else if (file.endsWith("qTesla-p-III.rsp"))
+            {
+                type = QTESLASecurityCategory.PROVABLY_SECURE_III;
+            }
+            else
+            {
+                throw new Exception("unable to determine file type for. " + file);
+            }
+
+            for (int i = 0; i != vectors.size(); i++)
+            {
+                QTeslaKatVector vector = (QTeslaKatVector)vectors.get(i);
+                try
+                {
+                    doTestKAT(type, vector.pk, vector.sk, vector.seed, vector.msg, vector.sm);
+                }
+                catch (Exception ex)
+                {
+                    throw new Exception(file + " count =" + vector.count + " failed", ex);
+                }
+            }
+        }
     }
 
-    /**
-     * # qTesla-I
-     */
-    public void testCatIVector1()
+    public static class QTeslaKatVector
     {
-        byte[] seed = Hex.decode("64335BF29E5DE62842C941766BA129B0643B5E7121CA26CFC190EC7DC3543830557FDD5C03CF123A456D48EFEA43C868");
+        final int count;
+        final byte[] seed;
+        final int mlen;
+        final byte[] msg;
+        final byte[] pk;
+        final byte[] sk;
+        final int smlen;
+        final byte[] sm;
 
-        byte[] publicKey = Hex.decode("6B8495B1C393966CE20EE722515892480FDDA8F7848245573308D4F1C3C93C4F31B262002E32BCBFBCE58F58C95322F9158CB10F30ED2DE4E1F202DDE2C4F68143F354A52F2C27B12B8E434E14EABC9C61B421708B809CDB3908B75106A11B09AC4EFC19662DDC818D4D2A50827D6197B8405C3C9D1AED8A513D3BED08CC60BD04B028D50CD55D9ECD3640EF81047F505B3705AFBDE294DF42EE9E488D647E9C7D532724ECF6501B18680F4376EEE1E0ED1CB4DAF5861F19AFFCA2092C8A6845224008B437F352695B7827E55C7F0BE5A49B464B4654280FE67A137F69924F8529FD947C5B1E9953BC86808243FECE6E03682FE46396D5B174148059703694A74D9DD399B64714EFD666874AE771E1C8A7C2E73D03EBB82731DE05324BFDC8356D5809A40835393B78CF577778B6DA061B2E640A083E5748A6D88CA3A1B106BE566B86C3293886CAF38BA93E16267BE96A0E91E08FD8683406779C1528298FC98101B1ABE625200B1296C50170388B5A5432A020E2D6DD7B40524B52FB960B8FF3FDDE3526842D355AB860B78F9A4AAE588F548F7EA05F94ED711646D82D4BBF8A22244FD9C466DD71E4C314B98235D819321052039F5996C216DA9A1B0BC897A1DA04F2DB48944CAA0C0F5BDCDC10498FD1E3B6A76EC8158ADB9280F7C0F43E1E835C178B114C939F94DE20974301D9AFB3D5D4CD9D3C24152F891E8019BBDF5B74E7212B85E5C6C90614219F54E674334D1D3A57EE5889F2E6C334D2475FF899FC50DA49C0B106F05810868F850A95411E6B616F56397221E45A363A2A4066AFB5A56CCFCFC3419F99B5CF87C977B6AD926504062CCEAF1EFB578EE1A2EC6CD6D1244EB20604E94D37C283238105B0F4834FB2946B8523F308D0020B1C8557403EF85235CF9F547842D6D94A8DD380BB06732EFF51FD315CF111BE138A7D118643B5A8921FE03EF309169539D7779CBA09C6079BCBBDCF84C383939E55C9F042FE5130E4E31EA33E2412A9F3B308F781E8353F7261DE83B20A5DD66210F05B71B49BFB499D60D8C67E2CC72B67D25E6B550BAF745F0E11F29A185406752B0109EEE7CB503B6F785DBEBA42270E9AAE1991FA41B5758A9D0CD74C6823EF23D4D62ECE67370E0732C43A1D54DC61F54CF507C251758E6C78D6DAA5120E202CAFEBD58A25A933B4B48E1A78ECF36104467A7167231918E93AC356C4AE2FCDF284A3B0E9F19038F8985204BC35839804484D3D4194E1E72A7B22FBBFE03F00072A3C05689FBC6E11C71DE2748826A40261A6357904B76ABA722AB91DE9B3C9FC66034B2C06AE45625E8071552A80A807F20A3ABD1D094512C64B57212941D3190638CE3FE69E5CC8759FEBEDDFA46FCE02B822DD32F1751BC41F4EDCDA63BE023154FE075275A28548450494D9743AC52DAB26971C78371AAA3D87B8F000E95FD79EA6F17B5F614FD0626C236F87072C56D8F7F7EEC167323F79A26E32048BF7C43C00904697F1E4C93D018D7F0845EACC01D3989D3502E2891B776B11CB349AF9C462E83AB1039B30688E153D7C1AA62C46B8889E404936912BCCA2517C059B89EFFC1FE1488C2EE3473C27805715ADE619BD3645D540C84D187441A46C0D904C3C9E2A92A92FCDD6D86380395A934DB93979AEA13C428EAFD9259CB3E2478DB643D0D6913AC275ACD2387808669D2FC40FC6011A87AA1F03639D02692BB18322D6417808A902A403068A7A45DC62A5ADBEC18458AD4F91BEBF7FF60494AA7C0A02AA6E067A86430CAAA3536056EE44944CBA38B7AE9016D66621703A77BCD36BD104BC2A040A12577DBA2FBF4B89AE4F49D202C27681B05BAA8C4026E0A60D44140A2213CAF78BEE9047A10569ABE5C568FBEBEA276DFDCF905F8398432129A82AF45CBD10BE668D0C60F4B64327388B830B1EDFE4ADB67EC5472541AD29C16B732A5F2F6B8EB1DA671B6F1ACC4AB510694F2AAE8055127734591BCEDC1077B757C036ECA8E4A114A649EA4730EAC4309B07DF98A5D8EA838C84675A7D10EC93E1D36704B23BD04E27F635AC6DA31959BEA5A10CA37B7193664419926D965E7F1916EC44B52B4BA993C5FBE705800B2C070B09CF36E0908FB89F71E2F7AD9448");
-        byte[] secretKey = Hex.decode("E2FB4F00FFFF37F07F1016B87FFE00DAD38F3D08E9BFBF7F040634B0C2FF340490FF0205F87F8106F3075080FE0F105080FEE9679F0006FA4720C110F4EBFF7FFD13840080FFE60B80BC04003C307F00F827807F05EFD77F800217CC5FFDFF0AC0B0BE0500701F80F4D48B9FFEFF184C70FF00E9DF7F4110180810FEFFE1F31F3E03F79B3F3E00FAEBBF7E0508D0AF80F52BF0807EFFEC3F203EF61CC0F07E04EEE30FC1FF02E42FBF00E65300000A2674E03FFD1460607F0714F08F3E050E9CF07E03FB7B7F7EFBF4B76F3FFCFF6B303DFB0D981F0202E56F60C000E37BB08106FFFB1F80020808F0BFF6CD43D081032354A040F8CE63C081FC070820800401E8CF7DFA106480C000F5DB507DFD0C7400400515D81FC1F8E32B10BE02F06B9F3FFDE947908002F41F50FF07E44FD0FDFDDE2F91C2F9DB23F0BCFD020850BF05E4DFFFFE00F5B76F020DED573043FAEFC72F80FF0B4000BE04C8A77082FC22A45040F9E7570040FBF713D04000160050C0040E884F81FEDF9F0081FB12D02FFF0211980FBFFD1768F080F8E313803EFEEA6B2002FEF9B3BFBFF10B7820C001EA0FE00001F5D37FC1FA04A0EFFEFC3CE4EF7E02D9234080FC198070C100E417504203057860BF07F68360FFFFF82380C1FD1C90B0BF051328203F02F9F70F8107EC435080F9FDAB2F40FEF21B20C10302F02F4104F27BDFC0010258F0BFFB01986F7E02E7AFEF7F0418C4CF3FF4EE0F10C007CED36F4209FE5B10C0FB02F47FBEFEF867E07F00F9BB1F41F40540407D01ED6F0F41FEE48B8F01FFE43BC0820CEFA38040FB01AC2F40020F54E080F412D4CFBF0118A44F0305EE67A03F010B04A03F0819B80F8004F09350C1FC10544FBF00FA7F800107FC9BCFC00724F82FC10A2F74C0BFFB0AE05F8103C9D78F3F0601F02F7DFEF66F107F00E85F70FFFB1678CFC1FAFC338F7FF701E86FC1FFEA27C081FAE81FD03FFCEF87C07E080BE4AF81FDEE63208002F41BF00201F83BC0FFFA0BE4CFBE0416E8BF4004109C408008F5A72F7E00DE835FC0FEDE93B03CFFC7E7AF81FFF03B500101EC5FA03F01E0F33F3E0600B44FBF00F01F200001211C40FC010D8C200005F7DB9F7F09FBA3FFC002070C7080F80388AFBFFEF297C0BE0AF92F507EFBE3F79FC0F7071C5040FE00E0DF02FA1FB85FC0FE0F504001FE15846F8003036830C0F7DE6FB0FEF3FFAB4F40060FDC7F010DE80B7081F70D741080F813CCAF7FFEFFCF3F81FCEFEF2FBFFE07E0DF7CFE017C30C1F9DF673FFE02F11F900004203C7000F9E04B40C205F107B0BE0225EC9F41F8F9D77FBFFDFE13D0FEFEF5CFDF4000F433C0FEFDE3CB9FC2FBED4740FFF608E80F01FF022C8040FAEF13A07D02F4DFBF800331548FFEF9FD03903F0312B46FBFFE10C0EF010224F0BF7EF90834D0FFF620705000F4F55F10420021DC6F41000D2C5080FFF11BB07E021CA0CF42FE2FF08F8012FEB3807E07CE571080FFEF9F5F3EFEE30B80C00512CC7F41FAF96BE000FBFB13B07EFF0BA0DF8202E00B9041FDED4F30C001F2CB60BE01EC0B20800102E07FC2FE1710D0BF0B231C903F03FF1FE03F0533F0DFBFF807EC2F000917445040001A58EF0001FFF36F40FFE627B0BC030E3C907EF5F2AF4FFF08F2D72F40020E683F7DFD049C5F80FC13F42F7C0006949080013118207DF6FCE36FC0FD0D348FC002E4B7F040F9076490BFF7E56FB0BE0903B04FC1FEF623208408F06F303FFC128C4F7E01F967603E14F447908009D7E70F830BF1F30F7F07F9B31F42FDF57FF04004E3D3BF81FB1916EC44B52B4BA993C5FBE705800B2C070B09CF36E0908FB89F71E2F7AD944898B305452F9EFEA0237F6BACBE4022FC80E5DE2D66D398814A7C835419435744");
-        byte[] msg = Hex.decode("225D5CE2CEAC61930A07503FB59F7C2F936A3E075481DA3CA299A80F8C5DF9223A073E7B90E02EBF98CA2227EBA38C1AB2568209E46DBA961869C6F83983B17DCD49");
-        byte[] sm = Hex.decode("553082050F6A8BD54C40E296C67011C2765199D81304F22C3CE306AE9C05235FC8EE08D851E2397C2956795D02F1AF944E54E2D270ED89FD6F89FE9BEC269B5CBE7F0184B244F89455876E181F39E1221F962B34FAE1A8F6E4EEFBE882DCD5BE4941EAC3ADAE263EBFE5AA9E0BCE6D2AEF44A12710B4301F07545EE129974B27536C2DD6C38FE9D5F7B725DA66C8B7A92F439C6721AD2CFC389176BC6A39FF44483C06B9D1A554164F5FCEAB5BF317983E8525ABF7EF291DE0FA281791680F61012A06742836CC6E0CAA87BDEC0C7D451801979D3A993068DE6F601DA81615FA56CB0A4B315CA2CE06F21925D088DE4DF0CAAC3DD33E51FDBE8C0DD187BAA82216009DD2819D524BCFF70012A734FE9D936A413AED15B3D652258CF6C3B0FD5BF4F64B7EC9BDBC9251C01A34AFBA707B35BA4EB6C392DFD6F93EEA454DCD2BDFDD689B5A980C21EEF124843F5571E31A69AE097A25B3CB1F04ABE269E78E098B658201B8BECC456147F759B14C3A3CBC00D1D7E3344F05E433C1C82DBD4001C6AFA8D948E92D1686C64F57436F6DD9ADFA82F35CB3814DDAD7AD0912B62B6B412A40D70F49E05B305BB5DD319C5C89775F27D3DD1884B5936A54738F816AC81FE328AB344FB39CDA6EC0B64D47556733815D2D83818D88D65094933E6E9A9FEB7E433D3EFE0B4C98D7ACCB9EA851AC72134283116513EDE0802BF959ECD5A8AFA9407D8EBBBD8ECB48B24CD034C3FA763E83782D6B0B2115929F94645F9378411251692C8858BE725B9D8AB880054956E71A75D5DD3A88D1476C36064A3C62EFD861C4A25943037ABFCA2DA9363F34371E868ED76D48E068C3A078757FD8E8588B356530F098ED81E3C21652E1071A6DE3F8610B94BEF7EFFA5C5A955EA3987B0EEFD16E6996BBDB563BFAE6C769AAF95E4CC4E42634A0CA641297642C92A57AA8A704E35CD8A759366E1DFD2006C173D31BC853A1089284F043E1648DB047E944EE26AED13A07DFE7C6893998A43CDBF9520A9E077DE27B36F03FFF08FCF4C4F6F4076C54EE6CD7F93C7C1297A4917B3E430981B312C2E1ACD923A419175C735F80F3957418BBACFF5F09425F047564E2F9FE4A9445FDEA63657FD25D2177A9C5A2BA11E54B3D0C06B2323735622AA23A69C14B4AA78BC4310481067E68C0225CBAC0C40A21E8676B5D2E82DE06BB5BC95A3A4269747E9BD3895C0C29A4B25BF85AE0C46959B6AC8BCEBF6CC3039FA0AD7E4698CDCB73C44A10A3469520C5967C2B491777711DFF1F311FF16639A5C35F10FF7DE14EAB5A786E7CCF152BC45600CB3565BF69477EE7BFB508F3259C75EC87B7F57561ECBBC8178497F6B77D5C8D6C2D3DCADBD7B2C21B59333FAAED20C5152E35886B1C672F6AC4BE7FB0DC6D9F4478428141FD35E30691677A1E6B866CB91BB9962D4BD5462275D5932820FD104BF0E1D4E08F0CC2B7B1FF36D2A694B848F1B84475FB647A828CEE4DB1566CA35A908562DCD45AFD107453E4FD18B571B2F64B077DB9AE3BFA15F0C10BAEC410805EDBD34B6638138651F24F91F9D82D8E7CC07F1F5AD93B1017A375A4A9EACC85A96403442BF1D5D9BF18204011E666569B6F280B268BBF339B607D7C33BB59EC7E12E7F20B14BE770547F797E990E70A8F4F1B738B4EFC7CE124B919537CCDEB5F0CA8DC2EA9649640EEEED11489C0A1C110DEB64BDA50C756A3EAF34C4E1059560D3788FDACBC2E352F2B93BAE31DE89C0D19277BB8AAAF0A419C2C4C1B265BBA1B41A9719AE53A7DB5F051E3B08836E5D3087A94315C8B83E21B1217E1276FB90252F56E72CF9C406A9539F782B4BCF95CE4E55340A00899CEDA7A0C020AFBBA1DEF8C394CE2354E86B7752DC57D1CC94ED32A27F12A06AF8C72AD2BB0A4D619C7F1569EA066B8231BA0CDFFA761F54F1A574C99E1B24C53F33F3D1F34225D5CE2CEAC61930A07503FB59F7C2F936A3E075481DA3CA299A80F8C5DF9223A073E7B90E02EBF98CA2227EBA38C1AB2568209E46DBA961869C6F83983B17DCD49");
 
-        doTestKAT(QTESLASecurityCategory.HEURISTIC_I, publicKey, secretKey, seed, msg, sm);
+        QTeslaKatVector(Map<String, String> parameters)
+            throws Exception
+        {
+            count = asInt(parameters, "count", -1).intValue();
+            seed = asByteArray(parameters, "seed");
+            mlen = asInt(parameters, "mlen", -1).intValue();
+            msg = asByteArray(parameters, "msg");
+            pk = asByteArray(parameters, "pk");
+            sk = asByteArray(parameters, "sk");
+            smlen = asInt(parameters, "smlen", -1).intValue();
+            sm = asByteArray(parameters, "sm");
+        }
+
+        public boolean equals(Object o)
+        {
+            if (this == o)
+            {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass())
+            {
+                return false;
+            }
+
+            QTeslaKatVector that = (QTeslaKatVector)o;
+
+//            if (count != that.count)
+//            {
+//                return false;
+//            }
+            if (mlen != that.mlen)
+            {
+                return false;
+            }
+            if (smlen != that.smlen)
+            {
+                return false;
+            }
+            if (!Arrays.areEqual(seed, that.seed))
+            {
+                return false;
+            }
+            if (!Arrays.areEqual(msg, that.msg))
+            {
+                return false;
+            }
+            if (!Arrays.areEqual(pk, that.pk))
+            {
+                return false;
+            }
+            if (!Arrays.areEqual(sk, that.sk))
+            {
+                return false;
+            }
+            return Arrays.areEqual(sm, that.sm);
+        }
+
+        public int hashCode()
+        {
+            int result;// = count;
+            result = Arrays.hashCode(seed);//   31 * result + java.util.Arrays.hashCode(seed);
+            result = 31 * result + mlen;
+            result = 31 * result + Arrays.hashCode(msg);
+            result = 31 * result + Arrays.hashCode(pk);
+            result = 31 * result + Arrays.hashCode(sk);
+            result = 31 * result + smlen;
+            result = 31 * result + Arrays.hashCode(sm);
+            return result;
+        }
     }
 
-    /**
-     * # qTesla-III-size
-     */
-    public void testCatIIISizeVector0()
+    public static class QTeslaKatParser
     {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("2773A7AD9D114C05E70C654102216989125420019CD31541E8A191B142C2732FB3CEC5C9C070A830C50A960E4B07C8108AAD0C044AEA0CCCA544422AD17966A52704BEFF1F2BC92423D79C128B891392824FD4EACDBD5DEF0CC2733BDF9D0D0835CF08640BB61DF4006340013144DC913AA00D411FBBEC59D5A3D20E1A3656319718F82F683978FE820C2AE48349FF42B63F44E71DA1678C2ADDD8447E9B30AA7D083A9FAB6CDE4B44AA9668F1CC4F58432D55340041792C06F3ACE7515DC1BDAE4DAE329B49FB3C4F086A0CAA846AF3C52721C1DDBD2A88093563A6F9FA11695CAC38C838024008B10AE0C76DE6272E0198209E133FAE50410192782130B838E56EC611E707140595C9B9620FB0E547C10A757EDE0FA29606028E8200740307E9ED8870D9199E0976E30F7C4233646209684B2AAC28E4B343C28008B4A4FFB8E02AF2D01E61E20F9C5E02886DA498AAFAB60A702AA4C2E050A4691FA6DF8ECE316AB75A61B55E5381606877488C630D33CE27382BC5EE368C5579F2620673E79789B1C79C405B97A426E09E49090BCEA3FE40E01513EEDD9081129C0A0A3B5884FCE5CB3FE7E6B37BD08FF1324ED390E507CCB3271150B1D02F9F15ADC205ED0068E141D50DC078D37E4E7E283510115811AE1205CDD1A109BB114771C0D4FA9E8129556F1F505CDE6E0507B73BB32B0AB3EB84AD99586E3E3326A0B1BAD6C7A8E0053D18368AA8BAAD7D702618EAF33A3B5F17E5EA410803E552E4262256682CD27005C054103B41EC37EEE354CAF7AE0F19A75EA470616F94174C72F95ABB4B9523CC4F291FC787A990E8EF43D2B8C1A76AAEC10C095F1BB0111E1A4046F6D131E307B960523D1884ACF914CBE40FD3130272510A13133123AB70356430620A3EBB2728172BC56C0F55064C7031D6A461693C80495930C25A04F61E223DFECF29526CF8BF261FCD8AF44CD13AF9E8AB227A579F35B0955AD56EAF7C012265B0A839D951B93C0E6A2108942B82C2658FCF12E165ECB57B74DABC2D30CA246F9B4FBADF654C011D00FA144170C9E9EBE1A935D620C43EBDDD8D1E60C29EFDA04CC17407514887F8198CCF58092CC75C241CB34AC2F9477DC0675E8CDB2D6934EC7B3EC25EBB370CDFB79072300575597754E5609BBB94788FFC5E12E713222BA5D6A22BED445E13E170AAD9ECA0D26E22CD0B808E1090EF44D9BE210E426765B9CB2D75954316160A44C3D936EEB21DAAB7AB1E60A4F651F3F5003061D80E0E17C0530D4F15474C2B861B72B3FBA058CCBD14568236D40E6B3C359FAE089388639703057DD09806C9108EE6843F02F03492C6B70A421B01DB32C10DF88AEBAA3CB45D4CC0604479AC1CCF0C9CD76250AC3345BB60BA081462B1667A1A5C772B505B5D705223E8AC30FF1C423783E1977E62277D196517636CDA83A80A87AA54087CF099B1494DB8F63C166E183CD3E8528557038D9D826DE01963B471E3337093139CEC888E6C5B1249826D966355BDE17CF989254C8C35036714852B24D89BB666EA8AB5AA68506125ACD8A51848478A2C14C94F189578C0ADC8D04AC0E896F6357DF3AC17172B06B5B25BC4A41CE3AFB266E17F7990327CC174001F71ADAF8F5EC08404A3A74B80E4974D20B631C11477BCCC1164AB8F49F7D6F57AA4A77F724435C218108DDBA508285D3E412A8778035FED2E516DA7BF990AEA03FD7E91F05537FB5A962A9259A0750DEC465687304925CFE5BD8F044A7A1EE638D2C448235D2BC2C9958C21DBE152B8D9865E336B57911AF86F404723CC995AE595F888FB675C47C2BC311F60BB3E47B4125CCCE51AD3A231A9792DB6788A54FC2935E22313A744065387AE450F0168BC00F60BECE76FA41E5383B0B45F950B27D327379865A73C6239B481FFB4AE78757CF51F5ED4CE20A4C31E89E19FDA2959158578439EC56CF6D29B0B2F8C307D63A13E8073E5AA7CD2C0FBE55EC4348AB807E773D726754F72EDD526B788ED61D031B3C2F64803AD34CDDDD8DB86C0078AD3CDC728B0399401A0C28E3D322FBD576FD97CCA4EAED9146CB3F83D0139024C14E249ED6996FC6182E739C72B177149EA08FC991CAA73DC8D6729649F395658B33D49B21D221D65E0BC90A0AD85E0F714D06E2CFEBE84F163843B7854F9E83A0409C07CBD22646C1A841FAA6FDA61FECC495CB2C881CAEF7EF4D8CF54B52241DB36B68093D40880822E808D40C2DE6D0488F476FC79064A0C8E03A05C02C6DB20F3EBA8E0067C154F2CBC7C4849CF87F7F1C92B4EA8BA70177FD1A31F691168317A3D4CCF9B9EDE02DB044645622313807E49BEE1D481136EB26803CFA5756E177EDBE1AAE6B0C8541B4FC48CF01014E54B2C789241DE9C54ECEC921AE7AF8A427213689A4A2CAB6376719C25A896B45CC414CF762B91A9840210095A46F3281A6DA086B254C916663B7941E4CE5281E33D8E3712226B201DAEFF4ACAB7C504C727F16D2F11C14885A2ED080638B153A40B3B12457A6A3AEF950BEFF282A079185C64FB1CACF19B8182900F2C4EBE28B7E370A3235D45C6F484FE3F75449E499F07F2F0A4FB1AD6508ED8CEDA34EC98A4957EAEBF25FB8F43D16F6DF3E32705A82979E221CC11E63B0C883D1EDABD4CD5A0855812477BCC726D68F0B211420C068D60BEB2D2CE9DA32EA68657432CB2014904BE4248FD08DA1F9C5E455B32A1A1C9AC31C7E7A1ABD8BD363A38AD78734D1EBED954871C274A5D54C1B492F01149BC90848E04D026118E9DDD0B84C1FF22473C08D267F1581D566C5F62EAD72C79D75F565BEC6C3275ABE3638D2D003CBA666B5AE85AA6E33D807F72AC157F64390320F3D4203D0FE2B70EECA8BC174D98AE7461444CB33688FA8BB82CB81F84DE6085BA9B63A9098A07E78DBFE5A20161366ED941116C790C67D7BE28680D3372C19E7BB17433903ED4688D63D4CE54DBC0BABBEA4916FA49AC71377A7E2812BE6512D504C04F51A6B01E625E806DECB5D3F6E19C726EDE8428DCE04677B2D8A7083A8C5A9304B06302F01973A169CD4A0D04473F769BA8B08E069F96EB07AFA0A5A061723B5C537BB6B41AAEB03F736F1B00210CDB816109D148692D49854DDA515DF53E9F737E594EFAA8033D576401CAF82ACD6CC7E8E913BCFF0CECF25D6C8BE37CA3B0D05EFB405EC144E4BA8B43814814B3488C6C7844924F77D06ED8E9D4090F758C13226577265B20898D40EF82D8410AC9C807D6BD0DB7124D4F23468736555B584BAD6864130027C825F8F15AD4A6550A5BD9AA04C69D38D44BDE53B5DF9EB202347876643C250278D786F8D8C4A8C9EC33FA57D8803909FB0991D370AD2FE3B6BE7E0798D4282C05B27410DA4B1718F05C82269C3717392580BF0391F20BA083057D3AA392E29D5C81CADA50069F812E6B8EFF77C59CEF51D27098AAA219BC59D4E549767435766344B754AA70FD43877CCBBDCAE4D0D6FE8F30C94B87C82ADC3BF62DFC6683D910B13D4ABC4B6B68C47AE51317F1F51B16918E7D9E851C653330B01838ED10601494C8BD86328405C18FD20A62F384129DC5AC3EFC58A915A182369F1A1F088608F64B314FE1BA543325B72986612765D3A474CC9A6CE04E1ACFA49BBEF23DD0B0E4E40C320578C985E8C29A653AE7046016E067887E85C9C3CDCC456CF811A256FDD8F1E9E8660466372EB85BE369D3B496991A7D99198794DDCA7B64B4BA91BFD7A3C258B170213639302B4EB45A8C9CE7C2A79FE3E3FCB0C23B057025CC207A6D02FABA9E299FD95525B4DA20250A1B54FA196210681961A04B9F6862C5F87586BB90027C06E9B769D15CB06A7867F7329731AD18DB714980DB021C8A93D32A20858AB61A741FE8B6CEB89E6DCA0960B06286AE297A5FF8B99A42660B3B802C330B91F686E6F314B62B2C83E8512D93C5E29D689AA1AB43401D0A866586BC937F66A3D0711C20C9AC36426AE20041358AE8C3E1E6AF31E4DF0157EE582210B6C32F346481B7D888D34DA15238C62E1B43FA4D5C1C67F6EC4F25A62B810418D3512B81CB107D9E88711E64C4F224894CF80D09997744FD7AE52446A7DA4E10051D597833AE1B36C86A0CB69B51C27FEE1B0A126FD88AE54BC04291183C0919C4912DD5D4E9EEC9C40B65D58DC33CB0289B0FB60D11B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD");
-        byte[] sk = Hex.decode("F60AF7FEF8F7FE08FBF50300F8030CFEFFF9F5F60103FF0FF601FE00FBFA0E0003FC030007FE0201F401FE0404FFFEF6F9FC0209F705F7090E0002FDF1FA02FB08FA010602FA00020B06F804FDFFFD0601F60400070100FD02FBFE000B02090308F9FF080005FC08060103030801F90B03FA060704F9FA0FFB050CFAF412FB0114EC070BF4FBFC03F204F606FD0400FBF400020806FBFC0CFFEF010CFFF9FEFCFC01120909030CFDF303FE0200FC0600FCF4FE0504130D010CFC0208FFFC00090A01FDFBF111050911FCFE010BEE0302FCFE07050A04F616FDF8040D03F7FB03FD080503FAF90003F70A00FD0AFBFBFDFA040EFCFAFDFFF100F507080109FFF7FFFD01FF0506FE04060B01F7F9FCFBF305FF08060604FC0DFA01FCEDF5FA020707EF06F8F00D0800080EF0F6FA04F8FC01FE0102FCFCFEFE000306FD030601FF0308040104000302FFF5FBF60AF7F0010B00FF0009FE090002F402FD02040F0100FA0606030205F71200FBF70909FD07FFFBF107F703FFF5F70FFB0D01F90301FEF30802FAFDFE010608F80BF603F8FB0E04F905FF0CF9F40EFB0B04FF06F7FD08FF04020103FB03060208FEFA0CFC0A09FC070BF5FBF903F3FEFF0312FE09F9FC040A0EF5FF03FFFBF8FD010E03F708FC08FDFCFB0C0AF805F90EFE07F5FDFCFEFE0502010801010601EEFDF307FBFAFDF903060B0C07F800FDFFFF04FBFBFD06FEF8090A050A05FE0A0602F102FC03FDF711F70401FDFB00F4030DFF0808FC070405FC05FA0AFF0EF200F6FCFFF8F20E0AFFF8F7FCFF06F708090FFAF6FB04010207F30402FF05F8F2F0F8FFFF060206FAFD040107FC0AF305110E0502060B11FA0500FC0BFCF7FCFF09FFF510F802FD00F4F807F5F8FC0EF9FF0AFBFFFEF6F902010BFCFCFC04EBF30703F7F30204FFF10A0BF80D01F7FC0B0701FDFAF300F60204F60EF41103FAFA06F7F7F80304FEEDF205F9F9FF08EF05F90DF6FF0307FDF206FD07FEF7010307FF0600FAF7140300FDF70410F601F80C01FB06FF0BFAFB0209FC07F506FD040A040C0DFAFCFF08F5FDFAFF03000503FA07FF0601F8F30A020201F5F8F9F4F704F60203FE0505FC0A0C02EF020204F7F5F60405F7FDF702050202FA0B06FEFD0FF7FFF201FE08F806FB020100F904F302F503FFFB00FEFBF6FB08F9FCFE0407F60609FE01FE020105FEFFFBFAFA07EEFE0E02FA01FAFFF7FDFF0403000207FEFBF804020008FAEFF6FC0E0C0604FE04060DFE0D13F5EB06FE000A05FF0E02F7EFF700050107F5FF03FE01F209FD0301FCF00204040203060208070AFCFC00F2F40BF6FAFFFF02F90701FD030405F50606070600FCF6F609F5F504FD0D0A08FBFD0207F3F601F503FEF404F7090403F90101100E0000F303050104F4F20C04FC01FAFB01FF08FA02F304F3040A0BFE0CFB08F80B09FEFC0105FC0B03FCF1FB0904FAF30204FFF2F9FD0B0201FDF602EAF1F9F708010000FA06090301FEFCFBFEF4FCF9FFF8FCFB03020A06FB00F5060704FF08FEFDFBFE11FEFB010202FCF300F90C0105FC0604090402F8060A0A03FA07FF0C0F0C04FDFBF7F7FF0402F8FD050B02F801FBF9090C02FBFBF1F30B0901FA03FB070300FA010403FD01F303FDFF0F0209F7F506FFFD01EBFE06FD100F000EFDFDFC03FBFD03F8090104060A0900F508FD090D0105E9050305FBF500F707F70503F90706F10801FEF805030B02FBF701F60007FF03FD03FEFD000808FEF4FE04FF0CFAF500FB13F600F5050BF50CFF01ED09F6030305071B070C040C0206090205F103020508F207FB09070A00FB110502FF06000811FFFD0B0D060401FFFDF702FEFA001209FE09FFFE00FE0BFA01040308F6FAFFF6070309F5F408FFFC02FCFEF7F80B03FEF8FFFE01FB04030304FDFF02F90AFC0A070407FD09FEF5F9F3040002F2FE0D03EDFF0305FE010B08FFFA00FAF7020106010D0A061913FD00FA05FBF808F404F9FDFDF0FD0403FA020200FEFEFB0004EF03FD0B0301FF010A02F0FEFF06F6F400FEFFFC06050603021005FD0603FCFC050A04F9F804F40E060100FCF3F3030BFFFFFF04FA0705FC0E04F401F9FFECF9050C07FAFDF7040EFE04F9F6F900FD18F7F5FEF6F4040307FE02060302ECED02FFF902FFFB06FAFE0BF90100020BF8060008FFFB01F5F3FD0501F60D0B04090204FB0508000CFA02000AFC06F8060200020606FB03F80A0D0800F501FF01F90DFD0203F4FCFDF8FBFAF0050107FD0101FD07FBFE00FC080807F600FD0801F20200000CFEF5100802010206FEF006F9F8070B0712F6FF0600070309E8FCF7FC0BF8FC00FD030F0307010807FD04FCFA08F8F6FE06F8F6FCFCFF06FD09F5FCFFFC04010F01FDFE050E0D06FD0AFFFDFC03FD08FB00F7FFFD03F607F80707FBF8F90108FBE603040406F0F303FEFA01FEFEF303FFF202050203FB0407021104F5F7FEFB07FEFC070B05FBF9FE0CFEFB00010CF80C00FBFCF609FF06FFFCF9FC000201FF04FC03FB040004F8FB0B0301001001090003FD09F9070002FA06000D0D100508F2FCF4F10B090501F90509030104EFF2FAFDF903FEFCFE0906FD00070802FA0304050C050A05F90AF7FFF9F704FFEF100005070113050CFBFCF8F30212F8FDFE030505040C09020201F70BF3FFFF01020FF7FE0005F8FBFA0B04010801FD02FDF601F9FF09FD04FD02FBF7FCFAFDEF04F90801F4FE0FF7FD08FDFCFE05010306020EF70201FFFE02FE040705FB0905FFFB04F100FA0A13FBFBF60AF8050803FC07030AF9FE0D01F7090E0009F8F4FD020401FC060607FE0206FFFCF8FEFD0004FFFE0704FDF5FB00010403FA02FF0005FA02FE000AF207FA05FD05020504000BF70FFCF8FDFBFB00FFF5FAFD0309B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB1");
-        int smlen = 2753;
-        byte[] sm = Hex.decode("FB07A3E1D8B107FCA922CAC88C668B69F9BD54330712E3788663FFB44310B4D5057F5F479039A87C3B0564A2474D1A97B944E852300044C68751C09A7A5B8394D4D388456DE8535EE57238F2364DAAB11BE3983AE492D5AD2C9508CFA2A138F4666871BFBD394BBFD69E577E812A13D973E20D8B3E63976021D946B0B3DD53D343C344306B85942927A75EF08D7DA4BC31DA2B0F727B997BA243BC1294B30CF5399F58D9A1EBF5F2D4E5D3D3FBC580FCF2F6EC2457EA4C713B5B0CC6FC565331E9DC92CA419CC73A4D618ECF6A5B2EE146B28A87458D332768D93C3E01A43DF628E1EA0B539040B6D5566189FE8260A22F701EF93907B061275637359DB9BB6361A680DF9FF38B4FBFB2BB31CE0D1891CBE07C6C2F8E0832038239E92FD4F70F5BCC28BCCA1E9F800266EDD0CE007FE86052F1506DC45A8A9405F13EE63C963019776D11311AD44A01E5B27CA4C51E051BEDFE0DBB5A3A7ABEE5B245689AAE0470D5803D1308D78D8A15167F31D87A89E186CA8B5F6566CFC7148209485AEF22B1735FD487138A261D35BDDA857BC0A966A8D407463D1BE9EEF2387F2F93B1A835AAB97A17F496AD92654F5F70DE317120365DFF755077168DD6C3E2320BC025E361A6EB98873DE8F9228A223BAE4138EE114B926C48494CBE6065C08E759109BD18C41FC740057ECA699369737E78C15FBECCD29D1C6406819511B4D115C238469E7D0722D0F17756F2F9AFDAC4343500AEE4C24D39F7C932270796E110FDC2739DE152A42B494C69E38A2909CDF0FEB6E4DC8EC028562EDA3109ADD188CE142B8189565802D648067BDF6497E61758F9C893100C2FCD678DD5CB17699ABBD670EFFDAEE6BDBC510727A48ACF0E4FAAE0E7D9A04A27BB375671048CCB3CB2C69AB232A9C31F617B7608ED88EE44696A2F75F8F35D1938430D482C67861E7E63FB71BC6925F66D793C9BC30D0F11DEB80BC23E1AC43E5D2F91F4DD216AC0FC0379A8F50F290789E72D322E9F75433857D99E5B25DA53F181B52C61E1ED59B99D7D86333C69D8E7989F37FF92D3B51125ACA0C477765D2710DCB52804C620E39E85EF9F01F9C49E5C123B898952CCFDBBCE985CE9D3E3D9D7A3F254FD10614899B694AD59520CDA87DAD998A7F0F4118E14EF50470F3B970C89702849EBD5FFE168E387AE970318CB5642D46D8582013779EA03C956B893BD2374DDB002A14BB0FC8E2098E1612E47703EFE0A75F35B751EBA9539E02915AF108821C3C908315F1C34F578D077A78F91EB6DA12CB4CBAE937CB7246A0C56A9F997D58A180653FE8B5DEFCEF1BC6BA41DEEDEBA6F3D6B695A91C270AAC086D9CEBE4D04D6D939EE0A0C1201F10C39AC3708264DDBD4B1A1B7D245304F8A6AA4229A0F7599837D9ED334246B719C102239C7BB93C0E9EE6EC9DDD57C59D7B88E43E1C19206F356BDC92873F8D7A687FEA9408507B32C2F4103936FDA2A694F486E05129D69F36563ACACDE2D60F9E114502751C2C5A5EB27D8DA8F497260A7BA003A858802F26A630B69A554CE006D78223D400E7E757AD8760D1EE7C8A6AC629E28C5CC0BA8242FE96203D970637578BBEE77466A6ADB19BBEE0B3C894EFC692D200106967F53AD26BE7ACFEF2D3ECE609B89F6A940FCB948D458E89798E5DBB2D5EA7C75957FD9F53F58050CDACA4CF5613C4460C4E3638663D4E2A4975B8271BFD26C48207ECAB05712E1651C352B0621C3376BD713CF3A81DB0D4AB2C3BDB1EF54775BC980BB04E3872C9119890037AD8BA2A984F7F090D8621D442383A2EBB4667F885772163978B346ABECA297D983919D80745B061154543E1C36271D9F5E82D929D2AA007025E8718324E260E992528D0213FD5A5659398FCBDA4080B34D6AE7C7C7DB595BC0A7E0887EA5FC7FCC76668214DB2BA457C2FECC69A37D39C7C2009BC52F7F4304C26A260994395BCADE9072351DAF81D484876ABCDD876FA87CE76E5F024CC8D098351B777D9B908FA5D903EDA4BD732173E974DD36BA039FC30BA6F5EC7C60C507E1EC4A7100CC3042EC30FEA3905998483CDB77D18118C547805C41A26DD0007F73BE9447590DC544EBF8D6880E7E61104DDCEEF39866DB0868CF9C8C0EA3BEC50B2E608540C3BA1AC7D4C9C945EC830A002C15AF68BD349E5E2704F6C244EBC927051B1F04072411AA7299FE88A38D1963FE778F5D86C34DAE4983BC2B6152E84A65E820AACE1FD7BDB5E22606AEB8590EFE2A7A40D4159EAE17D5F4239BA3D2ECC659DCDD7406D76493D4092F0D4FFFBE97553A26D94B4DB223A8B62A135AAA6C692BA1DAE42929B9D4988BB0D7B06B14A60607115AAB791A3AE54D5FC0437954B9917EF638F0EAFBDA8C4AD68C997E732C071A49E6AB0FC66238629C6E027C1EBAE1FEF45874CB28C12298734F0E0BBF838BE199BEC0CE52D206EBA2C4AC6230762B476A36042ACEDC9DECA3D36C92492D998230155D08522D8E11952A3BB06128D11EF142A15376EC172B66294A7006647DD1831ABC0034F0E0FEF507D988F0E60625AA76A535C2388F8E7F5DE8F59997883280BE1BA98B7CE54C797582933B688CD48460ADB2602A82912768A34F9E911F31BFD39094E7D8918F6DA0955F03CF44C0D4B37FB331727F1C36CD130A9CD0CFB88B1F274EB3D58E82E0852DD6C80E0F368C43BA2BEE67139ACA77ADD2633788AB994A29A565380118FE843A74178FDAD308BA5F6A570FA337A6B1E9BD5B6018AAD8BC8C993FFBFA7015D7337477E28EC09D6D9AB9AFA9CBB96DC46F9F9304B0AD829D54812B8FCBEDE58CECEB89E7A992AC0400E7BF0AE2F90768D6EE308C78DBC7F8D0BABB8AEE8BBAD5DF00B04C4C7D70F8B3F0B6A4BEAFAE07295C27EB2FDA2E15C1D0BB5B4318944117FEF36A0A94BEEE967D56A679116151FA916727B66D174824D13DC80024EF0FFDE2C4C24781F6D24ED091F4CFE61B2151E6BC6F5DC08AFB5DEE5E53C9897805D76363DCC57B42472C57C8DD5AD2D028AC0AC9640C14BFB18E8D6D4E6CC505F68AD65C49693C42043595098DA51B7AFFF60D7B26EEF522B7C5D1281B5962E3DE06EE6A09253897379DCE3285DA42DF236431EFD2E60E255DB71F380FC58DA34CA88E86A243B20486FE52B6774CC17C58BF1A2E3978A61FB046EB18014F9485752A9BBA5D9A434F51D0C0A2AAEFF67B801A996E1189BF68A0D3390B94595018FF6F925ABC9F4E12C90B8DEC1BC7D2720F658826CC53082397E19806DED90481E73F99578AD4B1D21FADF9E9D808BA8ED362B3F86E6EF491B13F48F064C22D278016DB5DDB264A4A0A18382913723E8E3FAA13057872D75C158A056176DBEBA30D7C62984E046E2188DFC3AF9C56149187BBA63CBD1D52798B4FC90B95081F5152E4686D3B9293F379F2ED37F365FDEA9E15024FB028C4C1F76A49EAB005FC92692A09BE757CEC084F4856D28A13C36637688FF369695A1FBE9991CF2CA3A10C6646B36B0ACC0CB00BE7BDE17682842690CA18C17E690D937E55098C82FEB02907B83BE2069B336425277E3BAB065614A44FCFA606CE390D504E72A8CB79745C82AE0A259713243BBE863ADAC3496E9254F2BA2AF5007EEA92413327FAC766600619DFBF7C82D4A8B53C50EFEDBC76A199A8CA003D5BBF56BAFC0DBCAA0BF43E4EE53A0DCBC4C10945CFFF2398377EB3DBA89710C0AC3C38E484645A629833B204E68E03D9C02322313E70F20B2E0655146FC8E4D94464DDAB904534FF4A53FB8D5FF9C5B6787AAF969275E817D66525C07ED7966FF96E906FF6F9A3F0B61788389C1F779DC4E6457DB0C2E1056F28F589021211450772BF97643DC8351809DED81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
 
-        doTestKAT(QTESLASecurityCategory.HEURISTIC_III_SIZE, pk, sk, seed, msg, sm);
-    }
+        private final InputStream src;
+        private final String srcLabel;
 
-    /*
-    # qTesla-III-speed
-    */
-    public void testCatIIISpeedVector0()
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("624563CB6E4499327172AF7F15FE1893614871835EA6F3330DD94337204F88D911FFB91B06CF2F07240203F75353906FB7EA7BCD0A4362546D94E60D32833623CD73B1D055DD1C2533CE5B81DE05C02966275D289D25134EF8577C7A489B383D3FA769DD3C1C033325034B731F7F678E5E58426F372F0F24DCEE455A8C6B0E6A68796E7B8D36317B23641E7D4EDDFA1E4F4A5012F50E70222E36527A14A559F4132F025F2C20BD3C2C023AE2E249E94D23460C80B94006CB1E38769A510911672D2E48AA5E5DD54F38746C195CA954D6A909CE33759A222399F209FBCF26641029D08D4C0C3D51D2FD7DAB2A6A3779365B06720B686F052A1FC5A54C5B4A26962F224C194096FA7048372F037D5E9772315B956881615C72EB57A0B661E9851F5BB61A17755BBFB571CDD90FFE562C4F147846407266A43AD9D063CD7710256D69FF6F07164E7A41D13E66C60614EB7F69B621F83237633E36E7F16605DC0A89A774E425323BCA551F211D4AC6110C314F39B873E48062C00255EE6E7E5BCF75A94F6DAC0E182C44532468658A2A6A9A9B72BAD7715E0765A10B520CED236A4D3343FE2A347D726D6107046877FE583F09FC50F4804CAD603810A15C7BB655400533721E4D38E020EFA64E3DA8277DAA437465033C486FCAFB5235B44C76AB27ED5E5D7BFA6CB2EF6EC7AE008AB5320CF861DB605321406ECDB56837572048175831343A1A192E7844225B6A72E66B42C98C7A3C513D2B5A71BCCC329BCF20246C241C4B5EBD5F37ED144C1CFD173F6330F78D40C9A952192201126F79EB1E444BE27E3FF500FDFA0BC27706FF2A4381454634540D3BF44CF418613C087776C940F5EC1928E1190F710909EB43EA715FBAFA3E3C140396EB516C9664A87277AAFC139D343D512C65776056A1C31104645033E03EBEC24D99837D1A427CFB7B72DDF86054744ABBD62B57A8355C8D3994F84B2A4B2CC1B731529E495F811A73414C03FC1B66BB0C58105283F42AF9B2521A5B717D8A3E8DF411A85C68F706006E8B7D1114769E2103FB2B7601DB1BC28B0355D909AAAB3702904F96BA6FCA3152C4F042596A7854B13827222DE0925A47CE40F2AF3FAE9F19395D70DBAD28DA8E7AC34F5BD36E174CA352807F2C49645B05015B389B3666A679AF7A033C8C0659600F6761263ABB6B4BC2360F29267AC82D80905502B7557A295406D605D75828565046DAAE09B12080EEAE67179D53B05A54444B4CC77C1E2DAB2D298D785B841047B5665D3C36E3181CD7AB2DD49F78633661949F585E0A18530D7940046E140D684C033915EE5DBC3A07C34A4178516F43964F0E2E04647324D26107980F68FF990C7B8C0C47F12FC1AC384520485EF061DF373B78A12D80810834AE36E5BA37397D35AA5C4625B708C51066F5C54CB1C52E0A9D3CBB772819D44A875B78AC4A65D3EA7621BE271914770B8D363CA911B8874CA1C16FDE314725B409391933DBD96B5BB013C2723E307D7D9C1C41654049204837D4074FA12B46E25D39E1EB3F74194C08CA47453E2092AF3142D06950FE14087E1A80970ADE4E7C6F6D70294F3710743854AF2D0A1776DDE166895950DDF06B2ACD11E5A248D8CE52EFF276AF9414B0DE264B315D61413EB731154CEC10F0FC230801229311566E781A57F65EBDCA4EB0CA71C62F281AD139D105108FFD5345016734F37C039377F71B36B649661F425A2CF3132CD40FF6DD5668C25808441EB86415136A55F57A1DC5991463A00BB3C67CED334979BE64A192471EC95F9F591014D607BD23585E5F0E03B33AA6E11E70F3314E7305336F5BF5E25E25D33C985B2D86BA0C41980199C506CE420F12523269D62949904174B86B383E41255F64940029094C0E9BAF76636E6E37B6711DA70ED1510E6BE35DE7C34FF8613BF3AD2358161E0E7F58696A669FD50ABEA278872C58BC3B1387F93DA26245E1DF1A35D32BB51F372F1133975E5DA27B5E112B43508D5588EE21D7A2704A9F7B1E7C62454D6D4321171C3647841D4A7BBB3B25DF019F856B7DE1652F26132C55049BF85AAEF21FDDFA03BB873761A72B389B3217C238B1EF315D4778EF4643431E1327350F240040CCF90C324370843554E9C23F6075089FB94677E85377B74FDD6A174F3B628B3F30778A629F6A5D9D8274943110095C3B763E4BC9862DD3F1441DE30256F20FE5DF629DBA55AF182C373D03D1E707253360E69455388554FC8140E97377B38024DB1C1CFAD014949552BDE132F8BC1D066166B48566C4A672B9F07A3C32399CF351AC073665FE4D52D11077CC6E823516920843FDF62FAD5D7D081B17470F16122317C3C84A7BA54B988E77952D1ECD012F90BC3C1E4152FD577E927847A8F943F5B87170A933C7DD5C8DEB4D0D02615E7B66D5CD399D0613CD111784C27A27A71516174B0D9534CFB51B6EE67D77767B818D79444F4FF519397E4C7B87EF06B0826DAE7031CFA63B835D4E2FC5035F3118463977458C6F5EB97F9CC87D77F107C28402F9D616681C108FF14C432315A368071A6F19C18108833137A4DF25764754FCF509EB6B1B118029CD7425566F4569020F5EEA7C3F5A6B0F3B62E8B615333F5FA43E15484C4F71496420D331D7FD74445816273113BBDA6DD5434DA9537EAD8479F20C2B4AE7648B9B04EAFA3A36143F0AB364552A482B766ABCB33274C832F5B3687B4802E3E369804275A0F443C5B76113E7380ADF03BF7057BAA575521D75F1EB5F774A7353A94D9BEB1BE70D174E602435FE55B8863CF5F71782B83F1AB62C222D5DDC8E04B20D5EDE755CB89E61855A160CBA7F60D708238267FE727DBDF67B8F160280A363F04B5EA62F79ECA260A0DA31B0C016DCF509A32E80A2B91176534A36B345FB2018C17540D14D569CAD14644F1135E779D360078F1D3DD4790757E035A4466B1C097751C01FC0E32D6A0C24ECD16D027441A38D26A9125F0D13039F9B2650E64C31322DE2743FB5306D057C1AE9B2051D916C0E076439070328344C970E47651B2D9E826DF12C6724347FCB3E13C61E0B79AD1E67D747C1BD3A9FF274B5EF0143126EEE3F6A72E10F75A70A77EF46B40B1A2D5A39BC2A2933DC3222787C60AC73ABBD110E73698E6A00C8BD031B626583832490B67647A3499A9431F3446DB4710E24F32F3F321E6C87427ADE28DEE2287D6120C6D633E1036A1B72067B955C3F873026E01957491B5F8D60888C13A91136FAAC24DF284668B31602C24EA4EC4BB0AF4A444C3AD22620E0B046E0CE3B28186265BD34C2EF34A74A56CFF15F230F308C6547D7252D340412F37F279B033C068961CA220A6855624EB860BEE53E8D0C492EA668A81164A87F042D9749DF8B493E4F2564D437ED53183ECB06961A09A3E30C4CDF37013A7435316DD6877CF3E2556689320CBF6B504205CDCF0B5F600EEF300669F65123B60972CA14785E4B54514143193B889D17415769413927235A24BA7D3828A844798179A44914C9F94B47701E07233E5B4050B6044270930583E5381AAD35E1C906952C1CAB143777C26208F90AAFBB6586295F168758400655DFB852BD314E5F5935735D66090521395B56CC60029B752D5D0E2B69F70043DA3A620D6A089661060B04E7402D33B77E726A49AC7C4D8EF52819E704B8B41745811774611BA3F108CD3143BE976A4A310D13320FB3C92B41C353C3725DB54B091B1E2CD9BB474360159BB615AD614399D7635465761748078FFB19828B72A1B57001CE20289E0F02BD15BB5E109FDE3E92BA495C660332337B94EC05568E4AE9065D1B0664CD4D11CA9A0BECDC7AD4041E2C044456B77E4E3C2066A33AAC412D53E66E2AFB12B946079B330BF0852D033342F2914337AE04425A02F80E1CC55C60530C08692008AFC758749726B5340B29F74A3F8F4045436293870A91746FAC025EF07C7D1D065B9D120962F87255E21E6EC73277D66B353D741F2D3ED099670C560D929D5E352D7CB5BB2A529F35FE8E514E4D72779256CBAF0B382A2E082E08294E6CC83955B2FD0ED1C65AC2355674823854F807EE707F83F42120375D4AEA21D076503CEC7DD539645B527856973BC0F06597081B7DCC48D428130A9F3230065632201FFA404FEFBB7EAAD60FA6AE57CE312CF7A51703610B6B9D0BDAD64A09D402C8D670DBEC4146816D80723CDF8E4BC6DA4CD5AA540FF510626F12FCA9485DEF65045257B5B24E896C647FD61C5D12013B3277DAD75B21243AFDA11109826B7B7F5873503323EC665D8C11E5CB441C4065FE1763BF302B21DF7FFBD021763F6031AA36369B198D761FD1FF1925583733EB325F845D754426B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD");
-        byte[] sk = Hex.decode("1C002800F0A040010E0AE60380A0400001000406101850408100020FFA0F98AF9F7E000701F22F08C060BE81F9FFFFDB1730208002FFF60F0070903E018006F917F8B72F60417FF9F1E7BB3FC0FF8100060000CCCF8FFF7FFD001412B8EF6FE03EFF04090A0CB0EFDF0003FF063A10C82F7F8002040604C89FF03F8082FCE9EBFF0F50C0000304FC0104E81FC0BEFD07F8FB03C04FE0C002FCF611DCFF6F5F4081FBF90128F0FF3F00010609D41710F080C004030AFCF397105F400203FB03FC3F80BFFDFA06FC032CE0AF6041FD07F4FD3F40EF5F817EFBF8F733D00F1FFF7FFAF81F00E8EFE03E85F40A06F407C07FC17EFBF70BA0E71FA1810103FCF90FD05F6000FE03F8F707F02F60FE0403FFE3EB5700FF7E7DFE081C08282081C08008F92D2C2030A0FE8103FEE5236000C07FFEFC0EE60FC00FE080FDF712FC078090A03E7CFC0C1640A01F5F81FB0D00E01B809FBF7E7FFD0508F0B78F20417E00FE01C4A73FC1BF8100F901F0F74F1F800208000014803F1F427BFA101A2C10D06080FEFC111AC4DF7F40C07CFF01120C581020C1FDFC07FE03286060FFFDFBFE173038801F828202FA07F417307F3E7D05FC1B50E8AFDFFF8306F005DC172040808004ECE5E33FB080418304FFFF27889FDF0103FCF5FBF73F0021FF8300FD152020B0FF817D04FD0D3C90E0FFBF080008E03FE8CF60C07A00F9E30788D01F8280FB10F22B686FC0407A00FDFF4B40F09F817E000F16D8BF4F4041FF0602E0D70FD07FFF8301020204B03F20828203F721BC47801F7F01FFFEE7FBD7FF1FC0FCFA0BDAF75F30DF000406FDFBF3DFDF7F3C7FFCF31F886F5FBF8181F904043840F040817FFDF80DA4A76FDF00020A06F02BE0BF9F0202FB03040058D09F037E0100E0FFFFBF803F7E0006FAC3BF1FA182FDFA0E16C0EF0F014182F5071440B0EF9F4183FEF909D4EF5FE0FFFFFE06F81B1840E08002FB0E0814103000007FFB13F403F8DFDF3F00FFED13E89FAFDFC0FE010DE4378850600002FE12F6335810DF410203F5F9CBD78F7F0080FF00E023E0EFE080040206DAD34710BF3D010204ECE7DFCF9F80FD02FC170490DF20FE010107EC0BD86F9E417F03F2274038201F7E7EFF050CF4671FC0C08100FE0B14F8FFFFFF82F9001838881F213FFEFD05E4F3077060BF7E04F9FBEBEF0F1F827DFEFB0BD8C77FC0C07DF600F42F980F60FFFEFC00F257E02F21FFFDFD01E653F0CFBF80FFFBF703E86FF01FBF81FA04FCFFD79F2040FEF9000A0410406042FC0606F4DB67BF5F0284070A0218C8BFBF0201FCF90D3838E0DF41FFFFF2E5FBAFBF1F40800300FC0380D0FFC181011024F47F0F6000810102120CE86F8181810200E0C7D7BFFFBFFEFEF8F1F3CF6FE0FE00FD011634F87F40C17FFC0DEEFBC73FFF3F060A01000818C03F408302FCF90F60209F7F7F06FDE7FFE73F60C005030EF69B476F600082FF1202F4F7DF7F3F0402F22D0080007FC0FCFDE607280820018001FD051EB4B76F204000FEFC21E49F8F1F7F0105EE0FF82F70BF81040500FA3FB00F60FFFE010BE013F04FBF7F7D0210F26F287FC0817C071302E81F20603F030119CAFB4F1060C0FDF9FCF717F00FA081FFFDF601DC1FD0607E83FE12E6EB0FD0E13E82FE00041840E040FEFF00F0190410DF203FFAFBF6F903E00FE03F8403FCF52FC00FE0BF7F02FCFDF39F4F40C381FA020AE417409F810004F1EFFBCFFF7FC07EFB070010809FA0407CFC010624A01FE1FF00FD08EC0BE81F003F84030F02083040FFBF7DFFF81B0CD04F60817F06030C0490C01F408108FC0F10A8BF3F3F7A041200FC1700A081FA03FA21D43730E0BE7FFDFA0BF447B0BF7F7EFBF51138F83F21BFFA0706FE1FA83F40C1000001D433E80FE0C07C0502F2070020C00003FBFBDBDFB7DF60FE82FC010AE4DFCFC0FE800A02E4BB3FE0DF7D0400F4FB0F402000007A03FE21CC3F1FE0417F03E8ED2FC0706001030402D62B38F01F0100010D0C1828B0DFC08401FF073CF01F404102FBF40F1020A03FBE81FF0000FC87CF808002FDF8EB0340A05F407F08F11B10F83FBF3F010001F013084020FF800101FC4300705E807E01EAF51360FF3F3FFE090A0C28D03FA00000FD07F617F8BFDF7FFAF9FD051028B09EC07F01F7F11F2800BE01FA09F20334C0FF3F017E06F9F92FA84F1FBE84F600FE2BD87F5FC08208F72D2430A06040FD02F5F10B20A0FE7F7DF906F4F7B73FE07FFD00F00510F04F3F00FE040702BCA760BF41FCFF0004F00F6040BE8100FDE907686000C1FFFFF8D7FF176020C0FD0000F017981FA080FFFF0204FC4F805E0102FB03D6EB0710DF007FFCFD0704D8AF1F01FEFE0AEAF73FC0FF3F80FB0D100CE8BF1F0004FD0714C00F40A040010002F2EB1FE0BFBE8100F11F08C84FC03F7E02F50F1C50B0DF82FEFEF9F55718C040C07BF8FFE90BD04F7F417F0114FC0378F0DF80FA02EFE91FA07F9F3E7CFC0BF24F906FDF3D01FEF709ECB75001FF800601D2D7FF9F3F80FF09010AE8AF7F80C081010FEA0350F0C0007EF8FDF70BA86FDF0001FA0E162428D0A03EFFFE07FA1B28E0DF807C050A082848F03F407DFCFCE9F33760C0FEFF00FF0B18B81F000102FD05FE53C8DF5F007B00FD191CB03F01807FF5FB07B0D72F403D83FFF807A8D7DF807D8001FA0B10889FBFC1FCFA08F0FF2FC09FBEFFFF07F8D74FD0DF3D03F300E4D337B03F827F0AF41104D0D01F807F02F4E7F7D7EFFF7FFDFF12FA3BF0EF7F81FEFD0006ECAFAF207F06F907F82FE8AFDFC07EFE0612F83F301FBF7C010124F84F50A0018204F9EF0F0820007EFFFC07EE2BC07FA080000005FA3BE0CFBFBF7F0705FACF07A0E0C07CFDF4D93BC8BFDF3E0102EDE52758F0808082FF0C0CF4BFD0803E0204F7FFF7173000BFFDFA13163470CFFE81FAFB04DA1B70E01F7F8003140830D8AF1F417F03022CE4CF9F60BF01FCF9E5EB4F1000BF7A080BDC0FC8FF1FC0830BF7E7E3E7DF200084FEF301D0DF2F00828201F2072818C0803EFF0300F4D77FD0DF3E80050306E40F60A0C101FFFDDD0B809F603DFB04001EE0AFFFDFFD81FBF1110CE0CFA07E7F00F807E04F903F000104FC17E03F501F007F0BFDD7E73F60C0FEFDFF03E41F987FC0807FFB0414E41740C07EFFFE0516EC2F9000C1010703E6EB675F000202FE00ECC31FD01EC17EF60028C49700FE3F01FF001C0490901FBF7C00F40700D86FDFBF7DFAF80360D04F9FFF8003B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB1");
-        int smlen = 2881;
-        byte[] sm = Hex.decode("820713D8B1371DF090139185CCD8653129882FDC98C3FFE2E84BC7F2D80EBAA1ED2EF0270809C60D2AFA9E4250A237AA3432E612FC95427E40740031C6B8A6AFAC01A4D4430A8BF6A54F262C57E0235FBC4981EAC6514DDD8A92C551597E2E3C700C85C26FF62DEE5B72CE035F2B7457CE02556A6ACFDA6E985733E6062CC0AC1175DB2EC9D3D37F8984B315954C79EDEAB5B0B1432F6F25EC553971CB4DF7E012F19B9FDC5451EFD313FF71E8E97AB9EDE423A0F7D700F212B7A703735592298ACC16CA62BE985301E3B9C12D076E3C168A14F6FD5901934BED20199687B5236772A36596EEC99EDAD3362538FAC2CB46E0D1D546D112C90082307D41EB91AFEE0012E389D09ADAFAB94BC6C270087E769E9F1EF51B6F37E26F0329C925C87CFC661C65CC0CDDCB8955438DEC61BF2F0A1E644F4E8072DBDA1D3E03CB45C79B14DFBF8DFC9C220482B88AE60C1161B6D5B55E8B1126AD84C05C021A69408EC230ED2E377671E3E8662F57ED83D6C295E45A35B19E494BD75D0C2BC0FDC517D40B066E78A5F1FF99D948644A590998B6DE51BDCECEA27E13A118B8A33AB276563B203366F8790FF8FF6C617957FEF2873C32FA658D8F5C7D25F4665B25C73F7D4EF3CE91077285EB031ED4668A0692C3926E16AC928C4A331D8C79C8E93C2B93A2571D171B383E339608B3213462B20E56B6DFB14A6642CE0E2244C770EFFB68AB4D039AB34C172CA497F57F272030C376950157A32708E343F1AC11207254FE6A8F7CB958AD8934A5195C8D07378ABA0FAE7242CE322848BFB9B70EF852943E92A4AA8D604909DD0C3FFE9688B323E9126BD1D9E1365AE7243A0458C90269B565C09692C1FF6E72A5E687ADF21555427B77296AD64872F9BE9AE6556BF8A6FD9EF87BBD4C1D9D211598ECC04915387E3613A5530A3866B0084C25F3093796B92AC377F817E09E9E84B65AEE54E7D4C2D4E166F18ADB33A4F3886198A140BD7129722CEF4AC8B5E506DBDC4FF140F71B7B87B02410A25E5DBF25E94F8CA8E6E5DF3687CA1622F144E23A1859E59E45897002697B672D11D614AFCD9691607804AF9C8D7D165078265D9F16FDFCA92FA9AC36E6102BB5E4FBD25D771BB877A2105B03591D5A0805345440B1742DC9AB8979C09847EF3A92E40ADC3C6EE87A003955D688F0B99906185B9F4D31C34094AE8DA9189E828A235A7768900FAB1B192EC3A127586FBB1FBE7BFC7CEAE5738CA5654244CC0AE90ADCA3525F336CD557B293065D9457C1D64B400D1EC567863872F917E7D35CA192FD56A2119D3C3970512AAA3F20DF198E9D10C3938312461E2DBC93A2917BDD427DD92EA49CA4CA4BD2BBC1766AFE446805010683151817B73B26E71D48D9D11E5C5358CF5B366D6BC2206081C1603BCC6DE2429D0DD55DBD8244237A465DAC87184C366553320CFD4E5D14DB498D16A9CDCCC7029AB3F93CEBD090958CA3FD2062DD780B21705F77F7A6DD2517B3C2E23A950BCFD568C0D7AADB35CAB9FBABBF876E48819E1FCC137849F02FC9355BC9DB13CDB6C2AA9CF9EECB82AAB2A36F4105205EE3E9F098706ED2B5A2D7EA1A9378849D1B0690DD8898455E51DB42BF52EAA1005DD84476033995AD13A86DA7A2E388A929E0318F37C53C2D502DC0A4C21C88313726056F33039EF835F5491A2B0118C4263ACE4FABC611B0BEF2C355ABCA5E7DA1EFBD6D9CB570261B4EC5CA9F9BAC1AF7FB25DBF12D77D5BAE9EAEE0266000002F3BF104799157918F6227082E39317C7B48F93A9DCD2D5F6DB8F0D958E9F5DD9EB46E195366AAA188429BD99D33DC15E27047583DBD6A131B2BF98EE022003F124D8D6C82266236DBB4DDC44113784C7F821D58C75511AD9BEB9DEEDECC3DA9521895CAEB33660D9B95F66FA8A2C4048E7C1202E0A959E0CDB4E1D6FC304F7AA48B605C099824E7890B841589B49E91B0223F5B51862E5315D96E50348A74949F9F11AEE6C49C077DD119990F2F46676102738497B79E5959981A625A3CD5D8EF1086C6B7FB95F44B071CD18FA64052CA5E29062813ABAFE51063B94C6DB6DC70DF9E9B9C82F41B2C8E03B6BAAD9F5E484BC219AAD819D58F65C13BAB464DDA67C07D20E2F23ADA7610786FB20D8B6520DFD2535427C53FCCB3B66924322897DE74A10CFF1112FADE0B16D0012FEE2F852B3C8AA734CC413DFDEA0838C2F4821DC7EDD31D16C23AD7CA6B4E8E09AB764810BD741E0A7BA4A86A9F72B17CA2194C6BF62E315312F8D94B0D3D0F6D293D64F9D146DF8E08640E23855429D3F23D3860F63CEEE1E3F1B0E726F0DFFC74B494C1EBE52EA725089CE4F9F7B4F939B936F0592E5507C45B30C75844C97FAD4E446361347B38ACB22334EB7F79A6827754395C6EBFFAE5FABF096647C69D251829A50657E9B2A0EB455DCDAB45B52964C6339837D28BB76ADD4C44684C9D7FC8B3A559EBEF40EE5A2CC4DCA67BFF35DA1B736DE23AB3C994EE458A25B246A60BD602CF6428CBFA300320C1D78FF6498E8D2A504AAC0DC07E5A0D077445A78875F20275242F1D5290EB3B98171C272D4D686948D33CA4F3626D35C3C9376670AD9ACA8C420E5C43156A2CAB9D870E1D06EDD85E652D962BE186D96B2F9CD84585F67424D74004407C291DC1F454644B96E003BD562658D9B9C07807FCDDBEBE761223500B43792EA77AF20525EA0C1897368FC97097DB6CA4554D1AB78126EF40D6AE4A6961F8E90C4D3B10741B6D2AA3C89F0BC24985EEBDF691779BF1CC69B27E076424AC71DAF713295B6D6A620DBC1D6B9C8BE196CAC12E97970FF14A139D5AE21F9D802C695EC4AD957CB340760612894FD76C1537B0E87EE28B56B52A3276063A7E96E48C3108ECDFB2FF37BBD3E1B8AAB2D7407C91DFD7BB6F8CDBDEFBAACCC8D74E1E75B0298F0820DBB02EB372EB42EA768BEEEE93C891F2B768133D90A32D60FCC59BB9E62848F7D3C145ADBADA260461D0FE0D080990CF5C1DE9EC5104CDA10D6F1410A4693B5EED91E4F8275E86E18C2604374D1675EE902A5F8790BB55656ED228640E9043CFB7BDBC6E38850680F69FE51C60F6DB689911505F398B6991C19E5D9A04C4853749E9B5D90F8F627B47B9A490EB657E0576C587EF1D7A1E3005738BCB5503BA34B55888AC6D0E037D26463F827F67705860FADC524A50411E22B544988B15884DEC6FC463526FEF1459A1A47DEDA883B36FEE3C0915C42609B8B719D1E7D0AB1137D101FCBF22EDDD6A148D9EDB29A47128E13AE50F11B8AF5946532E85FD1F6F24D3084AD1F672EE9EF4C33C71AC6C7C8144589984E31A56E48CDE12B51007F4559C2DBD0031448E9A648F1A31AE84B9C457C59000AFE43576AEEFD34A8900CA19121C5B18BB8475F88967DA7AC8F5CDDC8F46B9D1DF7EFF843E65845AA03D3EF3AF35F6223B973DCC2367BE4CDE98464C51FE4556412664E1C506C86D9F691A494E70263CB841F1F0736AAA306F0A06673B6526CDE65478D7DB0FD9E4F49E7436D910FBF3BBD72FFA34823B83B78A7AB8754E9C6E76D0CC5033EC64391906A1B023949384F72FCCD85B4BE279FE5F69D04A6108CBC1706A49EC180EB6B9A93049F517CFC0E9EE45749889CD86C76B3E371BA5D9AF10E9F04911F6A46D734980A9B1D63C05C2276C5756FBB39015526008C3196FAA5C698B41F953045D0E366CA96DA5D46072B5AC836A2F87A5BF577453103EA9DB0816C9A46874DA2699777D071B27315C972C99E6453ABA11DECA1866E4296E451AAD416F1139118E4F544DBAC1993834CD5BF4CE3A83DDBF2177C2FE66BE72A13C93140DEAD9F9EBAAC1178B128D0A2726A91D3C07C388C40D1F000D251370E5AB73D5F429565A5B1431E9B4C0714E61F58C2918E73D381A38AC42181D3DEE092948A761232386CE273DD7B1F8B7C002CB8D9AFDA95CFC116A3DBABCEC7FA991726BE4AE4FE5D547C0B7206E9D53EF329BCF40A84C339C8394C8998B69ED6B372A4672DD79E068FBD7782CB3873526D43D8CCF4A4D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        public QTeslaKatParser(String label, InputStream src)
+        {
+            this.src = src;
+            this.srcLabel = label;
+        }
 
-        doTestKAT(QTESLASecurityCategory.HEURISTIC_III_SPEED, pk, sk, seed, msg, sm);
-    }
 
-    /*
-    # qTesla-p-I
-    */
-    public void testCatPIVector0()
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("EA7347183E405433EFF49CB63A9E736B39A86CB67125110ADF35536A44940BAAFAFAB19FCA5B8F11CF72F7199E051A9A607D75D093FD1DD7F7038F4EBB172F9549747FBEDB2EDCF24BD007610C111E032C1852E4A92A8EA33057606200B17C785B8BB0B2A08DBE93185F3B931371B256CEF871167BD21876B1D62FEF325BE2D0A716A6ACC3D3CCF516DCB267093035DA9359E8A698C3946D88200E9033AFA1E87A30A7A626056F944010A5CFD972399AB7C52FC0E87D255A589EE0D3E14C4BABCD5CA50A561E71428C98B72D413B117F827F0FB557559FB16FF4EBC73C539C43040ACD34FD676C1868F59F84AA1398A61E99C090A8E1A7C5A19CC23CD21BE2B9CA6E23F6A7E05E3E69AD6516A085D2C58FFA0883EEC30C04B648414C3D4DF1C87AE2C5F06CCEE989ABE455ADCABB6C84FD17D8A9D1158E91C4A2598A153595B4C2F0921ED24A49F758CCF7FC170B3959B2F26791572ED54AA80368D5800025F6138B595C7D8B6B8AB5F340399AAE0C821C4B0A3D677B530227B33525D9A48AEE067368A81305FFCB2E8615035CD56203EBBED33B5B1F5B575F920A627A770F0644610094EDA55FDB7C7BB7A723DFD4662780ADA0BA214D91AAA9C832FC29DB0B59EC14F4681FF05B6E5D087E91F891416CF8150604DF06BC7901B6ED33A5967E91799BAF9E903EB7967D393772F7C170D4ACFA36AA333BBE00AA2A40FB3A8D1EBE9D88926207677BC27BFEA91ACF49E009725D1CC9CA4566FDFEB762F4DADA0ED3323A5D48FE9D19B0B60809DE4182EBAAD5113019C9FB4F9216DAF2574EDE87B41E14590AF2B6E71950881DF07B5303C13EDD4D516FB059C7B7F1DC5CE6BEF6D2ABB65BC6B2D5A0BD555914BCF21FAB87D417F66C87C0F007DFC6DD6FDF22DB646F887037227A20AA1CDACE683FB7F9E4D4959FD9D361C073D22DBD89C4BA32CA1B9BA803BB8EAAE6495269030C24C08C8844427CA72F321303F112EAD94E3823D354730EF3CB2F561728227BDC2321FAAD7ED43B54603F94CDC42A06CFED0F44CF34F357CE3019973988AE101746513DC8054EE0464A8FA8654A724277C511D0EC41EA009F837116E9B51C9491E0D6E7D4796BBB84E854C6097156F8A77CC8913F3D05E8A478667071AC757557FA30958314499E377262C79EE8EF891201A7E13710F4F6B252EF970B9B71A050681CFB0B2147D6108F7415A55FF1E70CEEF738272B8EAFAE18907E6D64F6729F5EDA8FE2E779554B41846521DC530C3CFD36A60EC145D0FDB6AA61800A534D9853A6B364F48F54558DE0132A39B4E5BC1ABC5BF376A5BC02439498B4C21FA9C64A72A9A55E51078724BD235E3D75D8AA12D4186C2D8E65CFF70E93211D996556B67A0231CF46E80B1F258C8FADD5662BB8D23B84C8C5EEB2612B0BC2A7B432B321AF2253AB16EBA0D24CCBEBC6D9DD83DF22DEB57EDDE1677CA943FAEC92E560E58FA0009F7DC4673722DF8FD38299FBB34FB61239611DC97A6D24B1ECB4CD6DF5EC4658304D34EEC218A55876A338BFF8BB2470B7F8275DE34863B27D5DA1AFAC7A24039DC53ECB99079E0D7DA4E3A5632D97AB065D11EF17BED267CC88EAC42A9A7C119D1211724D0E3A68A54A6220CE471EC3CF49FA21B4541FBD0CB8257B6396E4F2CFC8BD0756D4C0703F5B5CD656268E98C043CE4ECF34A377B8846817DEB391CACC4050E797873A1F88E974106471944D8A2A25E80FD54024C95A2B5E496FFA076F7930EA29AED839AA58954E5228F2EF55420C048E3AD1EA894EBBD4851AAF0A8C99CACA3057C486A0E0C2F1763B4F3440C87BAA44705F563EFA435FC68D165D0544902A3FB3B84FA8D74001F4C314F106D4AF37BFC54191598D2A28869F46D76B68D72D5FF91AC1F0E31F556F2030C816EBADFC9F72A55B9CB5F9D4C881DDF35448140025651B9D0EAD4C59845119C8D3FDBFEB219142C3C6820032BFA7644DF7E7D2783E27E8786053B02EC68EB4CE6DF29BF9FA8B1BA2E8A6015419D0A3D491E884C4059184B9C6E7BC35908EA60BAF531EFEC1D2379389291BC5E17A31F1ADBB5AC09EB3EB845F3044D31706543F063644F6D5E7D6DC686750134C9811B4BC230EB1EF7E8B214D82E444B0D3EC367D5D5D0912B5D6EF40F3D36302D726D9A988C208567067B5C5D9521EAD36A23C8E9C956443141FF78569A8E0D9EA2BD815A3A9746CF4F1CE603E0894529948D873A67C8F244DC10D9B706E127D508C0A814E9132E1AAD7DEC1BFFFCBB98EC65F1BDD6A8A03DD08256EE50869EA3509C2797184BACAD88798492F8958327D5F1071A90F0698EEA7676B4A86DA0ECDF4F3EB5553599514CA78773CBDFC9F091124E62181C637793A98E02847143ADC41CB3987334566846E4D201CE926D23AD382CA2E5E153342E7AE8CDE9FDD76148251F09C7290ABCCA43037BEE98947BD673B1FE28C0951298BDC45317AF1896D0E4B91BDC2AC886D18DB6008D8565FDE10D5D87322A5777C66A910FA2710F83BF61B396E9F82C2D68FF4B524616261D444F7AE05B57B4A86E23C6B759504812C305906ADFB5C900360E79EF73F0BF0233F5181608ADD756B40EA15DBA12B4AF06B1952F3ACE3908F6AE4625202DB122B5087E7808963426B23CE9B85260C924A677812EFD1A55E08CE2ED673BA2D6DAC7222A336CA6F0A05996DA63269EC3F5BA62E24593472AD7818C1800DB5A727D0C8A492EEEB22AB981CB688853751DA34F34A6723591909E2197D1A4D065737AE476831C19F92953CB7F889EBAC46A31D247A9108B3450A8F3B4437D0208B0D21558AF4E31DA942CEA458DD88A605DBCF1136A2E47897CE17030984952ABC600B8F30292DA4274593CC71497E9BCEB9CF84986E4C2047BE8821F406F1E000E18F050E3DD8E7F3FBC923D0830D1A84DB2B08F8E0623E3480A20A48E20A2F0F37E144CA58283A97A20E12DBE95327D69CAE4234381457CBA8FA237F88C466E8DAA72BF89E9517C28BBF4744D3214EE8B3263DD64C06C78AD9D0CC42517048DAF4AAD330BE626F23338FB6819329D11C10D928C11FDB96BC2A00C269D7839DEF20580F82F7D9B48CE896C45F568A6B38F123BE6B0690722DE463690EAF8C8336F829D4D48E001A7DB63470A39430E6929BEA765B3E8127C4213658C6938CE5D566546528D60C8B938196BE5B3378755580816C8120803A46084BA1980D9F129D47462874022454DA5E040456B2F406218E315149D1B79F7115C81B7F98E1A1A3F61C96138152C0A1B3E124E630902E6033742F7C74946B5D2B4D08DFDFD29FF7479C313FE889B1CCDA14904E0CADD9963F21E564EC09202A1B263440E9CD8EF3C25F2F5D58D278DF530F3940B79D7AAD270497DB9EAB1536C2F59549CFED6E5F4A9AC9907E586357C7CA305170B622CC3A571E45E1891690EF11C1BE641A2E4169C2D037EF6130C7C70104D4BF844CDBBB6C805A9B210B853417F6E2E4C85CF4D6053DDCC94122D5F2CEBAB51E0232E0DE9B19FC5CE0B78D569884F4F2E33BC4B1BF6A370B7F2E317670A136FADB81873EA13178798FA48E9E1123DC5B31D88301054F0D964C3939C0CF1201DBE5B43C1CA9E95698AD80AF3EE0633F973665468039EB73C63FAC0E70DAFD81F17492B022E98C61950A5D50846FB707CB7CDC7C51BA2EB7B2648C22BDF81C17A8EBEAA351C396E2BAA27D014F16388F215C5DD5CF53D077A4BECD1F57D0BCE1CC415C52052934179366443F80EC1D2266A80EF5A4CC09BA0E2301219CB9DCD88F30639B0A5363CEC3847CDC0207FE65B260EC1A4B5D40E3870EBFC1C02F2F4591A4D666E2754A523D9F1A5E79B69FE553B8405E338E4587EE2D03336EC2675A95AE9A35D9FA2D91FF934D30D3FFE4C7635C2DDD2FBF852859AA91EF913263070E238AF16D140A470A8A4A2A38720BD667EEB08E3436EFC4B74A9CC9725A7F0ED64B55B084E8EA7EBD1ABCB8A21E03CEB0F9CE5E3445F226B5797613C030F4ED5C445DFDF886AA6D7230962548B53CB551F3E6CB2FFF4799AE47E2293AD0FB30AD70417BB9F12CE5E7CBF9835B593F8659D98840D5F93FD67E0C48268F770E13726687DDDF8FD41D5B729B1B800892BC46D46154449A230139E80B59BC827B7638E1DDB20C366583892B8FC40E5B038772E4055BB625996969CFBCD95374F5966B5CCCDE8EC98CA3170A43417A941FD1CD00348E12465568A3DBD075A08D282D84401553B658FC64E233C5D3E5BC383606C737A3581CD38CC7BC362A7E79D46CBAAC8A76C2093879E2EAD94A9E9CA4703ED884E56FCA8B4EFF34BA4A002C31C2A15258879610551BCDDC393C0E1E4CDB543BF3273C4AA541D107D3EAE2DC643993779E15D214B7FC318970FCC976C344ED4D147BC226D1F193A7026E949778E67B395B111F943D44CA98E5A4EED33272B13986D48902692325FF749F546A9E6674FD6B8F67901725B8A0543C81AAB10228ACF3AB5F7EB2CA870DB85A69E4E63153DA7F5F8D8CBCB254E2CF003E04362F4E94C6EAB08F53200158B8F3CC8E8333E2EC6D5A4DBB607D6B180D45CBF05A7A9A60FE9ABD902A2EF248B9CA9C516D5871C88AF24C5376A4B3DA19B96E454DC969BD4DFA81170C627A2F6A0F0D3D9C65BB03DC24C470944CE27E108F3402062614F878088945A201EAAE842C20AC99D743E650DAAC82970709EC2D1A6CD5A49A36C434496ED6F5E1558C0580B61312F63C195C4FE5CBF43BD8C3B189FD5F0766041E8DCF336C550EB66C4C0F2B9AA420F260D1458448D4AA5CCBE22340058D60BB0C7B21231ECB2AEB51CFAADD4D10B683248F8ED6D2B61D4D5C66EDA259B30101F71F1C19A60C987019F236C35259E7C39DE6709806D24BCACA2897CEC81BAF76205A488E6920A3792A0B906BA71B9B5095C85F6FC368ED0214F026A6342359CF05C768EF34F1CA5E1F30E76D57ABEF961EA7097B82F8B81B9219D9D5EAA4143F23FB4812CC8E8AA5C089F35E697F1FAE5F45B725560892EA7A99F9C8AFAB2ED9033262780AA97DFD88B0E810EF4ED113494B8B4556C46C287BED57E7F60E0FE2A93938C25F910ECCAAE27293243956F9D8E14F08FC0D6CF1E1F43E5FE5FEA8A1156C94DC94EAC01D61A47966CB7819AC17E8A6C9CB361F4B13DD43A61156D378EC627B0E7414A29BA7CB0300FF1EFA20CDC4A2D2CE25C628B5A6109F4542C23B6E2D3E27C808AD2B706399C23E392D350BF6D7C02F0926D9E40B2CDFB98971AA06DBE751854D8DA832824A68CDD67ADF855541F9B1004B48E921E69FFA23271AB2AC3F2F170035553B8A8A00C8C248E2D8A763A4C95A709A38C56622FB8F4E3694D5B923A2CD8E538F113E8A08DCF19655C3E5818586C334D4E09BC4703573D24DC77804B24EABCF3841F372CECC9954A56ECFCEE60431CB015C84FC6B61C11E15B5899A81D07EA453DE77551070EA5B484FD937CF5C3BA7E8380D38B474379AD0211E35281A4FF6A2291188853439D472E476B2E93FA44F707FB7BE136CA62AB3E9F02D2B864EE25B357621AC7C08AFFBE1B7D6C184428E7A59C0D5365E21DEA5328B183D540DA779A38CDF2B55B710A3B8181A853D04482829AA656C77B94DDEE372B77CAC11D8781F3C554D54C3A74583E630C3FF32063672E903DD4382A85E577043B0ED269D9A5523E1DD32974640BE7AE82E94A13906B19DC3521F1950E1A5FF054C593D413446B3646E738636878C5D2FC64BD602F7D731F7CFCBC297A979A4B2922972EFC28BFF3EA51CA87B504AB011FDECF3344216DC8C99921BBED2E697203B4E994E4F88CCD491329A4E15BA8D757E6804C0F13C0D37D9064B1648C435229CBA60E53A764D9CD4047FED34209F16005BA94D07E0C12E1C27C38406181C347096472AB7145FE3B24069CE1BAF274408FA690391B73CFA1CA56ECD497D5770E01BBB2B8BD424873CFDD8B3D1A28AEDD7DC4172A5489194529A880F1115AFEFF020224FFC7F1B199D7408D888F765CA6F75F4F000508A9707BEEC60E144572D8E13FEE95956968628003148332ECF06136A9504E4DFDAC8BB6BD4A1267809478E43D693A6E8ED6C104A05908A37C75087BDB5954CC6D0FABAA841B86F5DB6473134015F75D28CEC95E0CB87B0A687D37A704B9B7EA7BB9DEB76BA63DEEB2F0ACD7C1140AD02A7FF1422B3FB8CED19962738FFE453F561E908519451F884A015003E65D98D81A61CD8FD7DA2AD14BB4EB2E7FA80E28BFF59DCD12D9065B9549641FE599F41E582E1821C2CCAC0C4DBA6159E0BE7F0BB2847F2299EE23010B7114925C4AC8C2BBA87EDF43694A1824B08745686EA845C02A7BCE2D98F973D49500E79F563B8F0DC054C5F1D30E4826093BB5944D7095E662CF008628278D85C869A69B634AEA2735D4AD59CE0DAB044D6D2F94004A2B9C1591F2FB1F7F88ACD7F5AB252B7E72922287E4473014D26CC43AA3D631B6A0E98444AA8E31186F8A7922C3A6F911A3AD2F03F4372F143F89AF8B758DF20FF26255C31E5E8C3DA86816318AEE2DAAD6655A36CC36A375EDBDFDA241BE8DE13080163D59E6221A90299AF4D070D6658D8EEAA88553DDC03778EF9872DCAF19114F8D6D8B0C13ACB647C49FB0102D79A388148C52BAE905C9D787015463125B336193B495F16C8E35F2146F496EBF780C859EDFD95773B6A19E9E5E582220D2DADD167379F632D8AAD683CC9B1EAA77F141DA66B6F002339C6DAF929F67A57B14191A2829CC55EC5A940716F09FC5A9B08C23881D956A36E2BADFC84E5427A866E2E76B476B2A0DE1B9CD3FB6AE156C74A7689B08D25DCA0AEC4191E33CC091B181F8C67BFE2A5DD82EDFC226D49A1AC7BE8936C27DE05D243209276003A63875B43E20BDBF79D8CA81F7AC56AE94EF46CB3FC9BE5473DC0C9715BF8DAC744D093350B4C75560CB1C099681F0E440FD682A387E7AA70B3F335546F026C5351440A5D61E3C83BF96F07A46DE986A21F7E5A2EB0A28AAC2087C3896E933B708B4A44234EA8AE48946C6A10213521A553A020D7C9991CC31526A4E94D210F97E4216BD88504779F3EA21D52C2A205FA47CCD2405D26F411A9312D561D4CBCA0763311A0EBA45FA48CCE15C80419FE5CA04BF416395D25265280E0382BFDCC868C719874DDC795329E07CBF5C8FE83A3CCC4DDA4B31B15702AEBF561F53634315EC8B1A5423470DAB46963403E10EEC58E9E1C5C4DCB3935B518D4623E6C8C68A4FB13B492B155E06EE3D16BCE6C3E79AC891742AF3B8D01767542EB6B7BC8EC71579136ADE2BC39FD07AFA207C3D3012229267DCDF6CC3E9DB25AD378F836763BC517DDA13CD264C90983D73E2C9E2703740DB4C38773B49625967933F41FF0220F40C976385367675005FE20AECB7FBC1EF5C71255BEFE4A39CE59ADA83F148C5E4383BC65F0440A1F5826537EE40CD55862E7239ABFAA1D458F64F7423668EFE8E1E979B34EE18A71F82BD1975E09EE1613D9F45870B98B9855D28F3C00DA8B9740F50E0C51E506DF1C7C804FBD2A1DD912DEE604B2080069AFC5095D23D726D94552EB7986C14C86ADB5A1306C6B074F5D3605A6C2189C4D22E1C8795FDB77803C60E3CC04B82A021BB89BE2E3C45AC5B914966D4A3DDCBC32A760ED7BA6291B6C728F13AC684A6B6F0ECBEFC06B54249FDB3633A786E0822B1A2E43C3F59011263DA540FE2B4B5D31F5FE2A0954E45E106E1255EEFF02A717BB9061BDB528CBBB7854956824311A755B68EC1C179213C418AE21B7DCFFA9F9DFD8CAE58FA1D0B88670E5A25934E7F07916844AC026CD5E1F768978FC0F3AF91922014D75E50F4253A2C2E3420DB1856C206CA20651703D4AE01AE22B8C3DAC60B8961586986F17A93258ED44C83DA6276E04A9D0E03B04E93939FBBDBCAD6833960F02337AB1211E72BC180EED3DC605052CF86F31F1960DD8DD46E848584E909800DCF13146B6A71A79AA92F2BF8D254132423794FC2416A412B001C47A9875FA4B866868A9A88487FE410F81BE5236B13BCFE9302BA9443F3C4523CD3ACE7DE93CBEF6D9E4E6090214494027F065AA834CF24F6CBA9293A02934E52F20C1C2BC9609B559846B643BF49CAE7FB8332BB91B719E1F1838D7F63F5C3A7D5107431E3B854E03EBA795DE9C717B0C9D692B566138CB338C3E5C6F5931DC33214B74F3A116BED19B91E27572A5EB875F14EE81A8B5F2CA2C98739C93C04611335D221EE922C62F02E304BBE507E82E329B4359807F595D11BB5CF761619FF685034004A5ABC89BA4DE82FFAAD693221002F399A5F1D11302B68291F85079D10AA2D1C4536C70C7739CD16293EAD510CE3A04D91AB80D5BD2D02A93C6CF5BEF703A2273E628EA272987167F8A3E2F8E53C3FBC907C2A5AB051653051266470D74F14B632AFBEE256AB24CFC2C7F6E035A0580EC3D1DC11DB98C7FE605183884CF3D7F24C4B3A3D05A697F95601C367EDE314FD3D7CDCD618DB63BB7599E4577D6ED62E891CFD85C9B0E43F0CA588CF282A5E5070AF8E9CBEA9F1AA1FEC53CF411C3EC301967130267B769177AF5508DAFB62F5AD7D8AC9445C62E59612A54F77B1606BD9EEA24998726ACA7EBBCE63F73C143A56435B3EEFD63310A6DF908979DE38D04D7CC75B5FAD9548071D7D1647EC6AAD9663DB156622FBA093B1C470C535BA155FF88EB90BB7CAC9BF4CD367F8CF68CBC5129A577C31BAEFDA834CABB52E0D02FF1A44FF9E85BC88F4BE69DD5C1EB9D4A547395DCDF8DD82C1F98252266BA21395B4272A50420000A40A50CCB22546CB50617F2D7FCE96DA0DAA8FD668D3DF70F3639B45D25910B8970A6F26ADB6001DBEC9D78E94D020307793CAC8A4E69865CD8B7565765C4C4686B0941F7945095A4ECF808A46930AC66F0B569908E97E9557400D265AD10D2A4CDD023D421922BAF3DFD93D7120E4F2C4D20900BB32AE8242295B3EC9D61252BDCE14AC6EF5B2F73895DC27E5CDBD878F24369A533B33DD67ED239C61C333C9FD879D267446F1DEE3837B66C410E3AB8B8BC4C782CBD8449C7B2CC89E81C0CD0DBBC2DD6FE3AE7F2537DC3BE3E7EBCE49EB63A76FEE5ED0209650BF6E06344FE0A46693855AF3D87FC61DFA621D5CDCF0A39C80F2715392DF2C12685F37390982BAF96FF6475CB436DBC6DBCE4716781BE983393D197902C1C757B831BA1564149711D3D8231E908C8458AEFE45C3147A71F87E046193C6BF35FCCAABF592E48724C7E0582077FF94EF1DAE47D911BDBCC43876978BA38C7D6372B23DF650AD5F27C95BDBBA2352D492B5873C3835ECD142C5D3007A4D16FA35DEFD4C21F929A8B02ACB71CFCFB9540B489F9F0C2609CC2E26C5E85847D06E82B7E53B37C698659482300A18B9C42E962E9CDA8968ACAB266CF1340298693C87CC18AF9A52832449EAB89E84FE63BCBD45AC3F9BDAE65E62EE8F97F1165E40D874E9EF75763972A9413C81D18A1E551AE0208053B6276841F8470C23BF48F493BE5A0AA94AC10CB69F0E60267BDAE69EEE405312F8056A54C47A80CD01DC81EEABEBD8336FFEDBE19E8D66C566658D0F8EBE7999BFDD8FBC9BCCC59480F98B5701C8CA3156A94794ABFCF3132445832DC9A582AFC83BA2E41B6D64C8A1560880B7DA9976FB61F1C33ED52506570AC3BC65E345629A00A611A442928BD908A93CDDACC7D8D997ADC9AD9EDC836A803FB392593D03A79440D4F2F812B4C76CBD8F01D3B7119E42B98C64865EB2AA1626EF1B4C1877F6E484BC4D776C6C47712E88E9AF422D676103A71EB6CB31F360FC0EE898A6C28950A448D77CC07D513CB6A58D6364CB1480892DEEA7585CC9774A42F6AE49468A65A7215D4112C5B38AE6516798CA6A1281042F435AA4D6872A2AD8FD600638F0297B9C732E3D08AF5EAA9E24334E56C57C931B3D5AD37C5E5A5255B3038311A89C600944C0946008FA7F433EFDF3C60B8CB0083DCE9A46247E7B8C790E9ECCD03CAA10379C13D171B2C912927324A818D3EB762ED56B1B1C6EF068453A75B2C3757AED2D04333A11E21F12060EA5FE58B4649970A194E223CEC7B9D2BE63D57742317E93AA66F422E0AD7689878224E921CC95B53511069CA6C5492779864E3657CAE50AA46CD8BAE0B29D8DDEA69F05D41EEF627749BA606F1A614166D143923E0A43A1822D726613B24F4B7C691205F6C6F569EDC542228BB244A6A133D6A97BA66018F9AC0D79884B2623442536F17D9A3EDCBD00975A5890A34FE87C37B718A67A085A2B3579822D6673A3041BB61EE24072B5B67961B01DE05F348A63E15BFCB48D3EE991BE1699E11A06E02255F522BDBB1F74A5E90BC7F46FAA51EC84D1473BD47A9DDF9B0C226DB8602EF74D9D6A54E2685D6631EF6AC744F51BE6609B30BAA0DFADABAC90F6322C4D6D0CCE589F09A659FD2B4FCD7F70874FC1E32C7602D4A70EE1F1A0424445A3A2015185C80838391C80637B369F43896BB990A00D55D0C44CE10A5BF10C853E7702B9B03AEC1CFEB983ECEB22C22F199776CC7EC01DE1782D27359DE58A4B177B138D59934A2308E937F4A6EDDE167113E87001C6D485974DC7ACD95BD3A0C79436EF4A9305EAF894889CE7AA3FB758C2C98800EE51C37F4204C11716384A997FDD4F9CFDF71723B37B8D4F4B15E457126BB98ED49099F0881E0386F4CCBFB86F246AEA4A2790D1B62B6430E35271025CE8C710A1413D293959B8264F6C41FEA8EB007FFAF1FE0B57A0672F9ADB6734C2F8FBE051DE3B3E80670F8169DCFB30CB6EA4A7A9A7E56ECAFD79B4A8C7E35796F94E89A1795D9B7E19FC40253E27967338BC4883E7205E2D70765C38D8256BB2504BAB38274CD9645F3F9C65CDA32A50D09C1B7F98F0467D9FFE3A7385B6473EC2012F265D06BCD1522BC9B82019163632C63049D60C8F79DAD64BD92EA183B3FEDC67B450B88FFB825E0F3C36443394D6FD02D61D42881823D8E224FB0D93F68D39FE0C8AF40C08C7E5A438B9B686F3280B67CF7998532F53DFC01299C7732A69DE3076F28D14769A324620C5535EED84289AA55B06FFBF2BB4D73FA75AC8EB66609C5486559FCB920A2F4217F7FC25184A27D75B197F6C2A479D6900D1D56CFADAB446F002615736C9D541981928E08B4C4A75B6D9E0ADFA6795E374D71640EB096BD017BC16B9A6E71EFECABECC9E85040D9803C88A5F11F2DAFD49B506CDA6AE0EA5BBA9A627C0E89FA6B0D35FA493E4C853D728B1AB214EFE14E39A999E4872857F039C67024E106454328641577DBC7B74F441B01A613576BF0A21950B079937BC9427464CF4428CA5EC21AA34F55A0F42005845D62355FEA077B9E6111DD2402C715A972F8FA43C317713C71255D834588DBDB63B8F8C1DC5D672B346135E138D835B36334A38B0B1514FDD87EBB6D8FB8DC41D13DA74BE01A1823E417D3F932083FAD5F2FAA0B099DD03A002B5BDA0F1997B65B62E2097B948B406968CF7759AA75936CAA33B3279A1A92882A9C9A7109090E0FACF49790B1AD1BD606D551A806B1EF434D491AE50FCE2FD7B17BD605236E4E20616F7B7E27CA1C0592F25F3AA967EA41A73D72F4FE4F241610D4F7806E603A1A188EED49D4C7EB6F34CEC63F7E8B82F33B16D7CCE26CAC7151F4A7674667406941291FB0B6315CFEDCA1039B1B28C0E3F29FD3AD285B044C3C24D317A4D83D47985A2EB0F661AF5589F647D92554407AB5664187871F4D22869948A11E31E59ABCD8A14E314CC46D88FD8E2F4679C473B02C3FA0B5704BD2ABB65BD4917C3E6ED2CDB400CC4C82F9A7B9BC71C02E0AC7A029C159E894A88426E60599BE62F97223D12E318037A16DF3C2AB4F90A76F6594297CD5580DFC287D080AC03B445A9278F51C61E8D693123262014A2802201D92241C1269ABB4AD0ECC02FFBAE5AF52D2BA5AB08DF8CEF1D5116C809497279E7D4D9754AA5BD616BF0B5EFFD40195FD0BD17A180BCFE0585F8ADB1741F52DC34D403993C71519314F103FC207AED3F205DC546CD7996E2A2CD2712E26008A74B292A75A1011BA15E8D567BFC19A97C4359F5209A93600B81CA32A0CB1333BB535B699C0CE95496EA897292410D8C89B2FB9157002F82CCE582D62ADC159C08C85357FCAA0F9A10D137F09C5CB1F427725732BF1AE5D12C95045DC4B8F04BADF4A3BD6DA9D99E30D0C79C3FC49763883F77397D1908A74D84FC9BE14BE0AE638B4CB261FAC04EE77AA2BAA5745BCB4282ACBFEB4945EC820CF507B834C70DB6ADAD6A3E5D0796B375B6A29B42D7564EF51E43943C708FA28C4F565D5867FD7E6260D44FCDBB491B4F1394120002271EAD1C1DEC08CE3E942EB8D681D837EF9387E960B33762B59459CC1812E8A847F7EAD81E0220F4F0A4896DAE94C2C6967EC578AA4D56D0D7CF72C53D17D57DD00079D10CF2E98282C2060B483735BFEF6197619182D592E3D9D7540929DEA260D6BEE7F38A21C267AD0B7194A1FD37082117529F2DBC64A39E170343ED8B8717796D802A57B57975A1232ECD48C79D2713EC1EF8F6FC731118B78F081B680CCB79A0A4232DE73D1EACA3455AF0331921195A8E70DA28D8BD04C178198D55D6DF1C5A8C1B641C26FCE25270A6D07D2C1B52A824A2C089988C35191A98A6085EF3A000D22452AD2C55DB0B87CF00C6BF2AB7E66E341C03BAEA55899AC814DC988391250A4D666A9F98F4A1CB16C80B23C4A97309AD0D42EA85A2776D3F097587648CE0564826CE4127DD1E7314EA9A9CF4910E17EBDCC40B4CEC74647B7271C3B735DC5AE8E958CC30617F01C3BD61B5515C91C75B98EFD8DF09D8BA8DBD513C5CCDBF7C5152C340500B09EDFBA42EBCA260DA419FE8856B4B1F904782815261B3924A5F9347673C02CA252B666D6498518A79682B321A8BCF2514CFE07E387DE3CCA6F92AFD4C31D2E83381A1FA741F5760845202EDFBB55582D3CFF2FF2B04A7D469825F1976CD2A8C388FF958F88B4E52C5916B30E6FE03BB9FF6D721ABAC2E7EC46CF6DAC3362929F00321FBB0BA622E1E87A279D5B01D43326D4AD95574CCB2B77E5C8F5E7E9670F4F1EAC11DC3716E070A8BA20E1FFB4FDBB0B75FDACEEC6E4C426C31CDCFA95DF4A1E4E6FCA2D6D674E641CD707281B9D5EDB5DEF9AC42A212A1DCC342DE5C1057E49763A791EE7635F8194533BB2541A690688CAC2293FF19F26B12E2CAAD572B616DCD9058918866155347FC3B7DCFC5A9FD84A9E020126E57CB6DBB5C3788D8B20C9D00EA68DCB344988BC7D92AC98E3E6A278CB536F0AD1A0D0DD8A60D2EE9775347EA0654566734F3687FC669D270A4FD2574770740E1E85D8453E4C7A8A97BEB84F229A630632B1CC90857FE2666E08FB105AAEC690B22B9035766D5A2ECDD63326AF9B48A3247D7F4007947B9BEC525611223B0957532B818B5A686A027FC0C5DB24A849EB573CCC55C204A2EA6927CB1E811F17E4E23D11399C3F88874B79B455D9470FB47833B10258B6739C120533FCC0A64E97B87DFFAABC3982A62DAD557F01F926B2D01A44DA5845BAB6C784D1E226539663F9AB7C59FAC757CF8C930D0BA7CC74CD98B7A60C755BF63B2362F854BD0D4327C6829E53421122BE8E46C54687958B142857039E565DABBC660F289F40E3036E5FCB332644C9BFE40F88DD932FAAF338188D4B7DE60C3D9C2153BD024ECC1BF3E7B653A3704A14B7EA6437B03FF83DBFB25413528AE9D4E8BACE91B93323BF6006A982D34981AD395D3E52BD33337D02A8370C088C2616B9CFA71CEA8B1AC23D3053F85C4B4B195B7F1F7B2BC11D0CD5FB5FA4FA64D95D14F0FBC26873F31703E15A74EA25CDACE3D8AD595A2EEFF6385406A2CD9F182C24B05D842DA26AD7CB762C76E5B255803BC6A281243700ED74093B6974B88EF179FE09D70483CDE0282E26EE377D526B088CAE8B72AF4D82DC178D71FD05BA2DDF7A912602EDB6153F45E3250571D1CA84D49E543B3717DE7E876D19C543F0A0EB8AF9774A6E2E7A17C679DCF2345D4775F292B150FD5789DEAD85DD1536E5FBD9D3445FD5A78FD7A9761A8D588A8CC97720523513DED7D6E95CDF640C7F7D0667B3A09134571A2E741DF8A007EA56765AE6EC046BEE94439E675BB49B6E32EA14F56AA3FAB2AA45FEAF65938EFA7D83934B5487896152F7C0409BA2572C18C7EE4EF61A5484963586E10F709A1396AA2565DCD9C0B4736B56D205851D2452CEC5E324FA4CB6B8698EAC2C9CE9074A17558F23FB7BC14868B8E73B424C8CABFADCD4C5A3EBB6903CEB7543FFCC5585B85BFFA94DD2AA324C505D7F08EDBCC0024DA4E5C1671B6CE012E605AA6B1BFD58D92E8353F05DA5893E3D3822D76338A4D8013297C5F643D648A53F62E8920674B58675C64D0C662FA08FBBC1E9FBC088FCA457E14608CB0D0272A4C77735BA3A7A26D1DB6DD2D0845178F4FE627EBA84ABD7679B2F86E3D9AF2171927FC74104A9F198733FDECEE23D2D51431641F657A85D63244E96D2636096315F291C019197E73C9E11B5C5A015C12A43D176F6D33E34D9C423C1050545E4CB89DAB3E6A58A8E2FF3415B4831A91E72718E5D09832C4970D3C3D600CAE926FF8A24ED9D14A5E5A623C7A777639C145D306F20A9A5B4DB24C99F824F40B5BF387A0E1B6B49468A62755CC437E787DF96748EA3FA5E537922307E7C0694699D227350BA020A5DECF680599AD95F00C0D73F50B13E8385C04BA03C0F52BEED8F4C1C1D9132A3B9608AA1039169A8FA850DDB4CF01966CF881C64EBFD758CFB245B486FE5689C20FDA833F67DDDAD4CFE4638BFC7F6D967F039786A330D8393EAC7A52150DD7D5D30F79A09CFA383A5B7E96E3B2EB6BBD66CB3D6D844B0BEE333E06D027E4379259465A38516686255550A6074B1814100A60F84BBC3CE560AAD08B21821CE740D90EF12452BD53516DC90E60A4480CA758401660FE1A6EE8A88B433C14698CCC86C4A270338DDEA2DE2A5ABBE1C5CDA9F2C222590AC705A0C86F927D830EE91FD1A3B9ADF35C4EA05915FAB0F988D17C522971B7FD2431088020CABA03543C1E76255045A198EF900099FF2A982D626A4DD09F0E3B573E0660E2B09A8C0E7000DDCFAD466E890A4746D92AC1877A53215E35B9D3EE718B92A14706FBBC87B18A42D91CD4017AF5C6D2F549A052A09B57CBF186D1E9C004624D4A3A7A8A03A89C47353EACFB90647B2C6B29F73C506835E320DB751A1EAD0538B74DFD39B383FD7042B51CD82CF10DCE0C984AE75C12DE1A5BD614045DC91989095D32250EB1137827EC5E0FF91D5AE8807389D238AC331185DDC332C90CC9EE4394FD9EB81CDF04DE8D22F6856C5BEE1B4E5739FFB19CC8A948D24D764947FCBC99218B4DA2C439DC410BE7262C062486D55AEF977C09A36D9262E2BAA088383422BAAA20F4D8F86C3A4D8422851B6343AA15C4A1DD5EF0ACDE63937BE7D7F7444286BF00621CB279F49B47835925D71E07FCFED911E94D378A73548539CEAA7D8D84EEF4BE67EADF7C7B57452CDC106033D2B22DBB25D05856C4617E144F4E23094ED4D2BCB7A5DD1DEDD661432CD2AA369EC0B32169D52314A8646699B49DA27FC6C233C449884F540068127850D6E383DA6CA607A0806561363928FBB259AB5D98ECCF00B697E1810CB5422AC9B2F12AF205CB3721B5FE1680BE7C8E9B89A6EEE126E75866EB44B48B3DB48CA63818E6EB691002B2130B6C591F2605152E382251153A042708781773146D57C7B16D62D5FCC2252CCDCFF10EFFA1BCF2D4305C34E4F19F07595102B0A23929CC5470B9E16060DFB7D0F28956492A5DCC8825FAB6133D490BE3BCA7141F049A52548BB792E8A84B0C6A2F7B13196BB8C27DBCDAE620019EC8E480E6521393C98866B04863B8736039F42C23CA001E3B83178E1A65FAC153CC2F32EAD32440FF6A8A4AC29762903B32140B49BB59E591B6480BACEDEC7CDC77D89C82F60E23B5B127B72B9A271996E2C9FE310346D739045EDC020668D69D7EFA248FD6F786FDC4A5199685D68084A1B4ECB0719A8781EA366B630F0C18D023F57CB302D92F8153FB99ACA4EC2E16C943FCB944650EF9733C97F66F986945B8531B65CA531798CA139D8A31A1CC2657515B0FF4235F0EA527E0EFF2E83B9F160857956C894E60AD59345C95D2319CA82ED49306307B75800036D0CFE6689BF9672F9D065678861F7E174489B789FAC5D6942A792ABAC3D85359C36D35286A51DB42C022B305D182E65A9D11602B6D2E88AEC5A556EDF3979E1660F162409FA90C3FC1377370C9389CDCB305772A225671345D30055A528F27AAA77819A7819F64AD7185202827EBE0D92A1E30B05BC17F45E8422E2A0DFCD3BF1347841339842F908E30502CEDFFBDBF01E2A1CA4C8F8BFD8489FB6A60F1EA4A2BEA49A9F986A9BFE1792F97D0842B74AFAD6E97902FA043A087F2DF276C5D30DBB274A824208EEC77AF7B84B0D4D18DB495E976EE802AD61672EACD514F7CBD355FC7F11673D9198447BD142B6AC1A6225851BA2D7CD3565CBD3D8A7341915B40F713165D6B4B949140B91AF9F89E46FD45CE4ECCB2C259BAC0DE91ACC11442A8C146623A0A64ABA992816FBCE99C835554B6ECA8C53A04CF70E81067BA2854FD2332E9A3813F9A3F1E039D4CE9F349FC1BF9327492DA648740E5ADB7E470B513B9375D439FADDB93A6B928440A31FF6F1184C33E1342CD3B29AD04C22AA2D68F4F3613450B7CDB2A3590CF60C03401949AEA3AF2060047A25F37B6B4D81783A6E3BEB0E6F3EDF523667F25D6093FFC23C0B38C99DFEAA9A6EECA7A7322ACA8417F80F819CE53A3090811733E71EA07B5D3773553335EA7BDED1128D48087FCC05E5860546186B657233B4A370B26969D7E179C49BE552B0D17DB34537645DA06F6B1E40A897FA632D1623303671A20F38971357C139602A03BD170783F6F061FA433986AE9747F3D235B662D29D1DB7F077004E34133EEBA14B2C271F3C160AB1FEA528C85404EF08A33A23256BB47A0E55AFF01ADB49845AF72057F2B9FCC6C9927EC5EFC0B510D65D81A123D082B3AA20CBAAA1301C425CA97CD8EBB2FC9D0DFA17C8F0010343BE3E4B2764A269C598B8E541E02295707B6D30968C75B5EF76A1E27B6C5190B818AB5D543E3E8525D007AE95B948B53E81E96D349041DD97AC579ABC59FE1AC5302691FA88F8453D90BA87796491EDE09F70C67422AE3BA6A5914219321E3EE34DD59379E412009869E6A611D4E0CFC7A56D645AF62E130779438BB89CA54BD04ECF012C94B59A800987B449BA086C859C91F1FF5D4C2C5A558342B2B836DD427D45C07D8789EBAD66C2445F5931EFCDD4EEC651EE22081953C30F7DFFBE1B98A8295A545F829C256060A36849FD80D4BEA694A404B4252D86496521EA0EF4BA1415BE3C3A4FE687E67E715D15C7DA44CD7FA18A010F46E8F9221466CE53503D3A9840FB77D7765B84D5D4CA19223CCB22FE81BE7215C8DD0E49C84D787FA921456A6943C5892539062313FE024A0C2E24D15D75A8097D0D393C422099BED9259878E4EA02ECF174CA3B8F5C80D01E998487123C10EC2E04043A4D87154BEB2087C7194F74201BDC8FAE3D8F202EE83A21DF9E5B3B0908C9321F4431CF108444CF5A93175B4563B253F84897E3F720E8EE87E6B268DCABA7017BB07C5DD29133B5878B6CBEF6DDE103E3E34D8BF6DF151A3A24C077D8529BCD02681DC9E5701F93939B5AAC45FACE46A5855E4B4C3AF82C303999937C48CF3D88734EAF75E3EAF171768F6F48FE2CD5FA9BF40328E642D2510E331BE5C3AD6632875A9BD51D6904B95BD5C17B24C43971D45055C7AC0299017249A936BA40E3BD17B63B510F4B7A3B4F236BC073EEF4F61CE561D893AB5B0A720D70AC138D47CCD5426C827E1AB13B2C140FD6E6903EEAE7B30A06851759FDAA28810A258306160452486199EDD0F2B44F32361E8EDD4A458A93012DDB8B4C08A565DC49EC6BAFCC759237AE69DA0DA41251D9E7E8509E4A09A8917418C6B2705CF7C463CFE64A67F88749CCB80B8B1B20A33B5D9DC3F1E74BAE981AC670EE2D86D7B4C8E891F3D5C98B3F5455423412C641C0315F27A263DE975D8D6DE1F17D6FA51262D8E6B3F32AFD9EFB72958460B5D58F4BB5CBF5AE1CFF2D2835FA294760854FF53522E2D52981FC276793EE5EDA989EDC921CCDC50D4B63A908BF47587E4FAAE3A8A1BCE3A21E331D9230B6B1C4CEC7743AEBB83EE003690D2CCB27301D88518A43B266A2E2DA78742230B2258E5D1855A990827B3A8B900362791AE617A3DC0BA2D12766A70316229028E09F96EAD7C773158A549AD9E500751D812D3F759468E22B86FF85DFB0C7C79148CE152A813D0B2D3979A3DBE3765102204ECDA68D24FA6330D599481516603607FA04439B9A0498BCB359F1EE20D65A5A84816AD43750AD1838EF4597876291C98E82E1F46A6963C7726C89470E778BD0A65CAD07E2A167AF6067CD058152E9C14979152103A7235DCD0755BDD793F9B23AE031133756EE3448CD1F93985B40EBE72F7F54124838E3787182147B84C3B3BF8CA901253E04BF808E3F3AEB84C05AB5CCA8D0C755D7B0CB02BF9E06F3113FFC098557DA3F439D397251D48D2FADEBC40286A17C66E0A8533328DB4DFCE9BE32F115A293567E4EA004157045D6996C7B73F27ED2F25536C2A381996257FD23EE4D962B663940C2A57442F5A7CB41B4E6615B223E91B55A7F98B416B2A6003D3A27B496F5E1A446DA9F0DED6A5AA0D7C283F34DEF97065049150B136B2D0F7A4207C904D836EB1E35BCA84EF2780068ACEF1942C2B31BF1F89CF218044A30CED4AF8743EF034D7F526989B99390D79D7454961C4606F6AD659487A803F4CF5FDA4F0A039664F3EA84AC4D7E0E805759A79B959906655FFE28609963BD8BEF5C82A50922A9836ADB18C9416358C092D69292C40FB3BA288D4029FC6B200E59C7C2FC9423360B11F157E5286D0A796A74A088E257F00954CA0789E3507E572EF3DBA68E3CDC7AE328F2072BEABC0E901D5F5DB1A1F41D14732F471F089FEA9D022660949C80D350B37B7F2F551DC22F665DE00FDA868F131E30C110CCB4C746663513E9DA22E143796EE2E241CDC47E2BBC148A230157C0378193F75248FE64858899B3B357CDC21CE8A6C79D2438569CDBE5A44015038C0CB8C260EA75698C272C9D5B169A4D2FDA21AAAB502DE3C4609707BA7C1B2E118F3F5B8A17DEE019465C6EA6B098139BCA2E6873D9945791F7D5488EDC2FD08E09973A55415ECC21556C422F2E0057387363A3D762FE41B054D3C5ED107C4628C15A409C0ECF77AA5AB911A7D896C9D106DB44720A42E0F89447B9E8D277859C941A5E49F4A577C0B6B0049972DBEA98A1E57991F6C0D239BA052CF369D94CEA0D2AC4A0F178D0652F9E028E12423D8D14D7C8ACE33F7E9FA9C2F8B987F89B1BC22190146FBF99B124D028A5AC57D4B21A8F2A2C9BF1F67841BCF3F47051EBD205822DD7F13AEF2D9EEC54D465D89C987851630D0F7930B4451914393C31468126AE0C607ADF304433E581A71C5A19356231A9C7D9810D282F3DBE109F6789BB997BB73E97650844F62FA1954D9BA9C227A23A65F500AB459AEC13D0D25DAC9DC885A01912C70005F440B0EB1A2C0FAD96C7D597DA59AD0199F7978D8DC871BCD4B8143420F5F9900600FEEF0D8C908CD259AAF1D446841B2F76827CE888E1B8A2C2C2FA63AA0C63764E173FF170526FA3D7973931E6C1317F9E7175BEC6F2D6D9FB36730A604879CE7B2FF5350D1A689A57C5D366EA0C219A8E839838A0FCF45A2ED7FD08AEFD2BEC15C47391D9D18E42C18F03B068DE6FC160C176499D06ADBBAD50F1773D036CE5AAA26247B4ED0B68415D4CD101C8B97567290CE4068F4CAE9A778F996A3831FFEF44CA682100850BB5B6290AD8CAA7C10878352C707C0E7D0D0D90462DD46C6317755E4151FAF1755106983B75B6F0A3FCB923D56BE32573D59062008A48AD6A4A87D6242FB13DCC2A7354D470633D292F3EC598A24AF75362BF3A4096C7400A1E97E31FFD716D75BDE945F8FE5A2A40C8C557A5181E7C4CF155F0E1F168A0F25F3FBD0D24C743977C12F1B6D0B4CA5042017194AC81EDE63A324CFE149482AEB04CE5209F0BD768C7CE90508DFBF5EA9A66A5A6FE038746852359919DDC76795A68A4B522C86D5CFDBA46BBE140DCC5B377F70FFD7B776AD3A7885C43A9A7B5BCE599415FCAEB0CA7EBC1D51573F95822CB3135B602ADF40AEDE049501F6BA26EFC18D4A5E9814EA2C2C4CF301AF331672896A9B383F7E582F3A09BAE7BBCB9AB088A0D7FE43827C393E5EC27F08BC5E85C4705EDDA2862BEC9A4DD0ED9BC56D592F6062F75719ADB6A9CF1C8C2526D6B79E4F53932AEF6031D30498587788B0569F6C0FB226105FE5CA0B8B5239C3D11C795674DFE2C8B46FE16DFE5C389888775E56BD14DAE9985960D0817E5354FAE5F20147B30001A9E8D89E5BDEC5567561A27A08A84E2DEF8182060C003A8CF5DD667AC830497A9D4BD1D003019630FCA28C1E272C8C2EADB1DBECEF8E2CAB9B60BAFDFBD6D75562294E0E169506FACDFDC559581B86559720FB517AD8A010D64D03ADDD2CF9CFE8D2FD244A4378DE585F2DC009B2ED6DAC2FEC87D6097A25CC720E7DC989332C5A341503B7D03CC57A875A1A26DBA88D840F24EF8C86DBBEF37905CBA382A8A5C9980504D0CD6CEBCF00F7335705BFAFDE3C3F82EB3225E5EC63D6D69EFD015C9C51AF71B23215E4BBA667AD525852CA6C29AE3AB303AFDE0279C45668D02A2AA96625FA7B41B268E783F829A3A2464F22436917940E6EB3B1D313F794BAD2514B35954718CB2D92CD7865EBA847D388F046CC7D5D3E3B57D12714E9BFD4A09A18A8A626CDDBB21FB599EBE030CED022E16D6C65085EFC6911F7D154B8C9787D3C4107110809219414377EB07AF5441DFAF02EED0B6C7A6BAB1D9902D7A3211F22536AEC7A0A365BAC29BF69021B46EC80143CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E");
-        byte[] sk = Hex.decode("FFEB02F6010609070CF80CFCFD01F7FBFBFE02010004FFF002020900F2020602FC090000FEFC12F6F6F9F601F6F904F9030101FE04FF020203010B0706FEFFF7040A040900F8050805FDF3FE070D060C0A00FE0CFE0D0D02FBFE0B04EFF90002F7FDFF030307F303FFF8F5F4F60505F9150304FD0AFFFA1500FBF409F801FEF909F3F3010201FCF90B17FDFD0101FC00F002F000FB08050EFEFD06F504F5F906EDFEFEF3FB07FB01FC0106FB0304FBF6080005FDF605FA00FB07FAFFFA07F8FAFF050810EC0001FDF107F9F2EC03F408ED06FFF70009F80C06F60102F6FBF4FB010207FB0A0304F9F6F90C02F80007F30D0C04F5FA0702FDFDFBFE0607FFF0FFF803F30D07FBFB010300FEF9FEFB0A13F007030A06FCFE03FD00FEFCFFFF070B1004F8FAFFFC0D0605FE0306F803FCF30506FD0211FD060507F8FCEF0CFE00FBF80DF20A0F02F8080011FD01EEFDECFDFBFDF5F8F4011210FF0403F8F3EB0D050212F3F80609FFF903F5F309F8FEFF0AFCF6000EF80BF6FEFA0C01F4F3F2F508FA02F8FF0A060009F9F70EFD04FB060005F3FD02FE01FC0DF30AF706090CFC0006FDF80AFD03FFF80BFA0CF30B070B0B07FC1003FB1404F5EC0709030EF701F705FB00FAF2F500000A0406FF08F5F202F8150F070BFFFEFA0603F9040303FC0206F8FCFDF6030507FF03F4070409080C1106F107F20AFAF3FEFF070CFCFFF4F5F10710FDFC070EF7F803FE060103FFF4F6F50D0B0202EFF30CFAF5F80C05FEFE070802F8FCFCFD02F81B100404080213F8F90C04FFFA080AFCFBFAF903F600FB00F60503FB0106F2FEE80003FBF9FFFD14FE04F50C03F70103F2FE00F5FAF600FDEF03F70AF70604FAECFBF70EFF01FAFE080803F20F0D1704FC08ED00F304F904FA020403F207FE0DFDFA040B1006F7F9EF06FCF909F903F7FD06F602080501FDF9FAF40106FA07FB0CF50C07FF12F702080A0900FDFFF8040A0BF20D04FD0F00F8F8FBFC00FAF0F91705F901EAFCFE0304FBF7F6FF0E0802FF0FFC020B02FC0801FE03F2FEFCFBF7FFEFFBF503F8F707000612FD0605040404F7F3F9F6040B01000009F5F901050202FCFCFA03FD1501F6F104000DFBF7000DFF0EFFFE0000030804F1FDF313F403FCFEF7090E0708F702FE0C040BFE0812FBFE0EFB0311F7FBFF00FC01F8F20405F502F40008F8F70FF705FA06E8FD09F8FBF2FB0704FCF4F3F5F9FE0AFFFF05020D06F4020413F500F703E7FFE30401FB04F9EFF407F9FF04000EFDFF0707EF0509FB03F506F9FCFC0400FF0900F3050AF50CEAF7030905EEF50C06FDF8FBFDFBF40400F902FC0300F6FFFDF7FCF1F014FB03FB09FE15FCFD02FAFF07FA05FE01070E0402FDF31102FE010802F902F4F90AF60408F9FBFCFBF503140FFB0DF80B0DFAF9FBFC0102FC06FA03FDFC04F90DFEFB0301F3010C0FFE0FF90207F80703FEEDFF0A000407F1F8000807F7051306FA090800FE0EF4F6F6080AF503F112F106FCFCFEFD0BFE020CF40DFEFFFE0F01F6FF08FE03F40B02F50406F401020301ED01080508F70907FA02FDFEFC030108FE08F708F6FBFD02FFFF0000FBF9F7030505F6060809000BF8FB00FC02FFEAFAFB030309F806020109070DFC030104FDF701F3030518F9FD0800F9F60DFCFF04F8F41108FF0202FE03F6FC02FCF706FCFDFE08FAFB04FA0B01FAFA080209FB0CF1050C0EFB03FE02FEF80CFFFD150803F7FDEB09F8EEF1F2EFFDFBFAFD0A08FEF7ED06FDF4FC03F7FAFBEF07FF06050A0307F710FB04F5FD0100E901FE09F0EBF6F203FBFCFBFA0008FE02FF0A00FA16FBFFF70BF80FEDFB0000EF01FEF900F9140C0E0901010606FDF2F4FC0FFF0EFF0EF8FF06F9080403FC04080BF7FA01F4040705FEFEFC0203010C090AF1FC01FC06FB01ECF3000B010E08EDF0FEF9FBF4FB0DF506F4F007F6FCF3030D0A04FBF9FDFA00000D010C00FBF0010100EAF7030DFFF608FCFEFB0207F601FCF80109020010FF100702F9EFF800F40109FCFE08F8FDF508F2F6FBF6010EFC02FE0101020D030BFCFBF2ED01F8010210080EFCFA000B120017FF051711FF03FAFBFB17F805FAF9FB0102FD09000CF90B1A0AF500FCF60EF7FCFC09F1050DFAF9FEFAFEF7F50CEBF5F8F3FAFCF6F6FAF308F6F505F6F9060A00EC05020AFAFDF9F50200FA0306F707F9FC150A000D0702070B03FAF5FE0EF7160D06FF050004F9FCFB05010107F2F50103F60305030610FF0B070A03F704F602EEF411FFF9F5FBF2F5F00101FE080BF303FB090402F30509031107020102FDF502F5030301F2F8FCFC07EE0A04F4FE030406040802F406F4060FF2020DFDF803FE01FC030AFF06F50A00FDFB12FB00F4EC04FB000B05FFFFF60EEE02FFFFF7F301FC0CFC08FB06EE06F8F9FAFCF5F30BEF01F4FE090400FDFF01F8F20BFCF40D0001F708F20304F906F20201FA0BFBF4FDF309060BF6FCFE1007ECF30202F907FEFA0AFBFB05FDF60BFEF5070CFCF60C00F5FB0AF909F1F40302F90D000503010705EDFFFE0103FC1004090C0007F80012010DEBF3ECFE09000807EFEF09010EF202FD0A04FFFBFFFD02F3010906F808030409F9FC0D010C0200060703040606F407FF0006FE05FBFA00FB0FF6FEF9F8091105EF09010300051304070DF80702FF06F30A01000102F7FF0EFD010500EEFBF802F8F6F9FC0602FE100105FBFFF7F8F00AFCF906FF0401FE02050A0205FB03140B040806FAF900F5F3F906F90FF9F604F602F705FFF8FE0A020C09FF08FE0FFDF6F3F6F2070002F8FEF1F90709000301F307EEFCFB06F9F7FBFEF9F50EFDFFFFFFF508FF00FEF6FEEDF7FC02F3150106F60803FCFD030FF9FF0CFD02EF0510F6020801F70AF7F8080502FFFCFC04F7FB090BF1FBFE0AFEEA0407F8F4FAFFFD0FF5EFFBFAF7EFF7FF000E00F906FBFD01F9F91A00EF0C0502FF050BFAFB09FE04FA0703FCFCEE0507010707EBF3130103FD04EFFE0DFB0503020C0D0AF7FB010000FDF409FDFFFDFB010BFBF404F0FC00F8F007030FF8F5FAFAFB00F700071205FEFF02FCFCFFF2FE01F00C020BFB04FB020206FA04021008FA00F609FD0C06FAF009F60FFDFA07FC090E0C090CFDF9F7FAFA07FB04F20BFFFC0500F8050DFF08FB09F30104FDFCFE02FE0AFC0CF9F70903FA06F6FBFF01F305F8000807F1F9040E07010A03010301010D11F502FDFFFEF2F9F904FE02FEFF09F4FF05E9110309F3F504F3F807F2FCFAFFFA0BF501FD040107FDF816FDFCFF040404F9F7060AEBFEFD07F50000050B00FFFDFD0108FFF806F5F60D09F909F610FB05FD09FF0C06080EFFFDFEFAF7F9F70B05FA01F4FE03FFFEFD00FFFCF8030D07040B00FE03F3EF03FE00F5F8FF00FAF9F80CF9FC0102FFFEFE0B0108FAF7F8FFF805FBFE02FF06FD0311F8FF0CFBFEFE060CFE00060200FDFFFA0C00F30EFC060608EFFEFDF2FA0405030707000108FE05FC050403FEF800060408F301EE03100CFFF700F9F2FEFC040800F8F8070104FA07FCFE01EDFE020A09F00EFA0C04F70CFFF002070BF908F50201FDF8F80AF90CF205020BF41103100205FB0104FDF50502FB02F7F907EDF3FBFC0E0E09FF0C0505050E020700FE00090609F60C03040FFCF8FB090AEDFEF4070AF3050D02EDFF05FC0503040A0AED0803FF09F50201150F0CFFFE0604FAFF0EF801F90CFCFBF9FD04FFFC06FF06F6EF01F811F807FFF9060407FEFF00FF060B030100FF02F902FD08F905FCF50700FC0D09FA0302F901040AEE0602F401FDF6021703F50406F803FEEAF10AFB01070916F0FD11000E060204F707F60802040302F60F0B09F802030001040114FC09FCFC0A0B05F5FEF80F04FDF8FFFD01F8F3F6FBFB00070704FF0FF70A06F3050D06F3FEFB040001F103100F06FA03FD04F4FD02FE05FA06FBF40FFC10F11407F8FB0AF6FEFB0303FBF7FCF4FFFFFFFD00FD05050900F20600FC0601F40D070403FFFA0505FB06F806FF01F4FC030910F70A03F20809FEF0FF04FFEC06FA090404FE01EB010106FFFBFDF9FAF4F91703F7FCFC07FD0DFCFF07000AFD07FBFB050CFA0702FCF60E04F9010603FB0501070AF806F6000BF6F4F9100BF4F90904FA0EFE0203FEFE09FAF3FD03F908FCFFFB05FC06FA0DFC0001FEF803F60200FEF80D10F80E0B030503FEF103140A02FB12FD0301FFFAF8F905000408FD070C03FEECFC01FFFA000F0C050203F9090404FA0710FE0DFE0FF4F8080808EEF404030909FE07F805F800F7FFF9F4EFFA0307FE06070005FA0FF5F3050A0400F8FCFDF001F2F312FB0D030E000C00FDF4050904010602F705F20207F708FA050E0B0206EF04F8FC000410F0F30DF7FFF508F7EAFFF9FDFFFA0504FFFE09F5FEFE1607FCFF0AFEF40206FC03F401FBF7FAF7F50402F3F8F9FCF8000903FEFAF801090504060307FEFE070E0AF6F501030FF501F40407031007F90206FF14F7F9F300FCF008FC0C060AFE04F905FEFC080704F6FDFE01F8FBF0FE080A0509FC05F803FF0E02FE0E05FA02FF1000FE050814FFFB03FBFBFF060CECFD01F901060705FC030B0BFE08F5F70300FDEE08F1F5F70204FDFAF4F6FF0705F50AF5030610F90F0C040DFE050001FBF9FCF60A05FE05060107FE0304F11104E3FCFD03FCFAF7FF0805FF0FFDF8FFFF050101FEFCFAF30C08F2F30107FBFEFF10F0080403F40309FF030B00FB04F50DFC09ECFBF506F711FDFEF401FF06ECFA0600020108F70CFC000DF9FBFD0111FA0002FA01F90008FF0B09000AEEFDEF01F506FFFEF909F80CF806FF0503100206F0F700F602F901F202F9FA1402F705F8FEF80A07FF0CFF000DF808FA0FF7110701FDFE0CFE100500FCFE03F1080EF60B080603FA06FB0802F9F30D03FB03FB020903FD080700FAF8FAF8F9F2FC0707FAFF0208F4FFF7FC140001FD090108F80D0AF1EC0AF909FDEE0500F1060507FAFDF9F6F8EE0204F3ED0308FE0B0101FE0C02F303F5FD0BF9F5F00C040102FAFF05FE0C010408FC06FD03F5FEFA030700F9090109FF0A000108FA0901F5FF00EDFEFD070705FB0600FEF2000E09FDFDF1020500EE0503F2F8FFFEFAF8F803050B0602FDFCFA0C00F708F40602EFED04FF0B04FB0115FEFEF6FF0AF7FB190006EF01FA0BFF02FEFE05FB010107FE02F7FD00F10EF4FEFE01010302FEF4030700030BFDF5F903F503F10D0904000A0A09000106FD0DFF02FA03F701FA02FD0C080F0D04FDF603F6F7F900F8FD0609EE0BF702EEF5F4F4F70105FBFA14F708EEFDF1FBF901F7FDFCF3F40204F9030401FA0203F103F90EF4110704FD00FBF001F60905FA04FD11F305FB0A0C0406F7FB04F6030A03020AFB02FD02FEFF0DFDFB04FDF9FC060B0905FBFBFB0BFF01EDF50200FBFD110D070803FDED0D010C0B0006EE0302040108FFFFFE0304F6070102EA0EF90701FD0711FCFDFBFF08FB06FCFD09EFFB0DFDFF01FFFA04FEFB0DF90A060D0407F8060D02FEFF05F90AF60DFEFEFC0F08FA0102FC01F6F6020501F407F90DFBF500030800FDFC02FC020AFE08F909F0F8FEFFFB03FD10F5F3FD04FEF80BF4F5FFF6FCF5FE03F8030107F500FDFF0DF2F2010AF90007010B07F2FFFB05FA080EFB07E9080614F6FE0E020206FA01F4F5FEF4FFFB03F6F2FD0000F60007090103EB0211F1040203F5F90901050D0F05E5F307F909FAF8FEFD00FEFEF9030C0BF2070605FF0806FC0011040309FF02FDFCFF01F404F80AF9F807FB00F5FF04FC0DF3F504F90DFFF900FD05FA0908F90303F50AFE0CFC0DFB0FFD120906FAFA0813FB02F7030107F700FC05FDFBFBFB020D030BFAFCFE1000F9FEFE050303FFFB10FB0104040B0B09FE0102FCF7FCFC02F6F9FFF90BFFF90F01FFFEFEF9F6F2EFFC0309FBFDF701FA12F605F7EE06FCFE04FFFB03EF0814FA05EFFCFBF0FF0BFB18FEF7090BF3F50011020C060604FDF015F706F30EF30206FE0A0403010507F5FB00F902FC0C03F901F718FD02000703FAFCF70B00FCF3040602FDF8090106F800FBFAFF0BF9F902FB0615F9FF00F401F9030709FF0305FCFB08FFFFF505FB00FBFA00F611F9FC0A0A04FE11FF021207F7FBF702FA010305FE01ED07FD030E11F900FE000101FBF608FD080F0D0DFC0BFDF900080F020FFCFBFC030513FDFFFC0EF7FF07FE0C09F4140602F70201030501FB07F30C040D0603FFFE010603F2F90CF70203FBFCFFF2FE0801F507F8FB0405F8FD07000C0102F4010009FDF5FFF2FE08FE01FF07070908F7FD0E0601120205FB010606F8EEF9FDFBFD04F80304150AFFF9FC09FA0BFFFB03F80B07FDFE02080DFC020CFA01FDFA0B0702FDFBFAFF05EB04FC03F1F90C03F9EE0F030B1202FA12060505FFFEFDFBFC01FE02FEFE1400070AF8FD0204F7F80DF10714020805FE02040106050C0315F60704FC1201080A0307FE0004100A05F407FDF60AFEFFFCF40400F9FFFCF9F810F00203FBFFFF07030A00010E04F5F106FDFA0EF80D12FAFAF20EFB05F20307FEFFF2F809FE05070800020009F0F6FEF7EC0A0601F2FEFA0AF508FE060004F606FBFBFC05FDFBFE02F9FCF7011100F6FAECFAF7EF030EF9FD0DFC000202FA0B0103FAFD03FCFAF8000F0703FBFDF4FD08FDED09F70B07FE0006F5F508F202FDFD0BF2020807FB0FFAFFFD04010408FA0803FFFDF7F00001FEF4FD01FDFB0E05F4F8F9F6F3F40406F709FA0EF805F7FE010B0309FBFBFE0706FE0B03000D0701FCF30610F3FEFAFFF6F80311FF09F60804EFFD0F080706F805F104FFFF0605FCFD05F9FB040BF614F5FC0502FA0705F4F6FCF407F4FAFE03F6130600FF06FE05FD0208FEF90AF7FCFFFEFDFC0EF6011101DC12FDF70407F609080D0CFD02030AF4000305FAFAFBF10C05F6F0F9FCFE0009F7000100FC08010DFDF805FDFDF5F7F7EC11011103F9060505F4FE07FE03FC0308040E04FEFB0305F9F5040AEFF9F50601FF010AFE0C00FEF6F9FE0BF80303FE0303FEF40A070D080308FE05FFFCFC11FFF501F9FAFAFBFAF9F701F9F5070703FAF7FCFFFA0C02FE0D07FEF20FFDF8F80705FAFF02FE0308FEFC01010C00F70813061103FFFC01F90CF3050C010F05F50101FB1202F805080D0701F7F302ED0AF40B0CFCFD0005F707F813F9FE02FC0101100401F90CF70B08FE0108010000170004FD06FEFCFB010106F9FFF80702150D02F902F8F1FA10FE03F9FB09F1F6FEFF0906FEEB10FB06FE04FBF5FBF806CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E4F5C254B6292FB5C3DB9561B8793D8AE3E1611423AC0A9F8CFC13E1C85FEC6B5");
-        int smlen = 2881;
-        byte[] sm = Hex.decode("498A996E2B6C14E9C84617F4D63149C4676855D15CDC54A85F1BA00CD5FE550C6120227A77605DE227878113DF3137E13ACEC54F42856C0AECCEB53E1E96760ED6DE0E3F353ED4E4BCDB6B1AAA5CAD3CE1DD882C6BB12D3CF9D1C9888624A54D3E024783DD2C39DD54EA4FFEB280B0E546008CA0C779C8917E8FBBC7A79BEEA32D5E6652FE93D8B4737D47D8DD0C1600D5DB9135B74C0ED6E94D9FF02D06F951FB680902009EB68C34028EEE5176E1D64EF869E14C0AB577197444149CC6EFC60871899B1AC9DA86F8373A1273A97BCE318F40BC6A2AF35CEB52A5EA853BA0588531B87B0E1170EA6F464948A55A84920C97AA9D3960B9A099E947608A1E393C634DBE5DCBC6F0BD55E853470F6AB286CDD126B52A29BE7465542D477E3386821901595597E4D6583B6E16326D4AE71A2967FE20BB755A12CB7037614D3600A2B02D4C8DBEE17670CE7B4B144C3D2BEEE9FF6049B94DE4011DC401687E83211AA37E7A9D043A0582A9A7DFDB0B5A3A4AC8CC58F9634DAD9730311F2CDDEE205FDFBF160DA631FB11D0D0DA9FB42223194B18A5850E80C3537F8148A965EEF7B9A2A81C69DDA4797B28A0FBF4FDB31559F940AB7F47021CCD5881934487A6CB751845BBABCF3D213BAB2C61D21F06927DFAD28581E2F8377E2A36319240DD8380BD85DD1A5BF4828AA4F8F5E11534F4ADFCD1B881DD8A094890B20C4F67F5C1545B4065B84F0B28A64630E332ED2426CE04754BD23E52A90299FF399B240D04CD6677E782409D7AE46F5CF718BA1227CE673C42B1CC0E759429F6486AEEF5CF9BE96087ADAC32606B3D2FA53254C62B9617FE0C8D0490C7C7A77C9D54DFF076D44DE2864E3AF3037511C05D2603354C2AC19D94B6CCB6C6362D57B85C6D3E40E5960F7D3DD00ACE24B07AC41014CDF58B106A6D61B0909DDABE19AB78FA29C5987E7CDE6D5D084C278417645995976C74CB7D9A7D6597A83CE1654209325B788F056220F672CCB16051EC94B34F89B6DD33859F6961432294DAA66805488E894BBCE31BD4BF29BBEFA3B987857A4EA2F87AE9F6B5EECB8BC3DF90547833F3240071AD5A5464CBC9F42DC0E9E96DBBFE1AB2681CE2DC100F07CFF7480DE919AB27A2F51204BBF779380B337A550371960757D3BEFEE45AC13792F9F885AC22AAB4A5263364078AD1474B206BC54795B69380B0056F9CE8325B7B780B6AF7DB44F3AFEC558CD93FA268A6AB9AB6714ADD72C93B5BB011D974F8D2D778BC476C5F41ECF7C7D48E632716EA393973BD67F7496078F937AA45FB33E8ADA4CE5192DFB244A1D8290BBD29F239CBCADAC9E1D260D22781B7B41E8A42ABE21FD0D3E3517AB941AD09013EDE52EB9081F731A7935B14E1A9132FF2EEB2C53ADDBCDD3A9656F5E22B0F6470DC961BC0C1B38BACE2F16649536D28271D7BB9C76BE5B63ED2663C5842335A783C524FCEE69D6964874D4BB56418EDB945EFB097001506561A1ACFCD03B15B42A009C9B150CD1FF57104737E7978185F980B333523FCD89F522A59055515A4895E68C07161C47320CF1E542F03EEA4CEBC76266E2C85916638CA4C054F0F085BDCF2A84B5666932A23FF8A40EA90F6CE2888731E88FB7D90CC24D66934B2FF378E662CC57D516072698CAF09EE3759AD305A30C0C222992D3A22DA664A25B74043D338DCFC6A70C786853AABF31B6B5C49D2BE0BFB0CF214467B511643106E729F4756B147467530F9477503BFB9393D36D4A9F964956DCC0C6ADA0A4CDA17D794F7C6A32C7D8E146C6769B810F97F9FF47BC187C57158684280AAED7807D2FF6B9AC3F4E11CEB29F112C30903983FBF38F5D118F084A5ED22E020F066BD458716B491DB9A9A26AFA5C187FEC28BB67C878F82EFAC22675C0D2B6D62DC1804D7933D68441A9F66419314D66540F30BC7B1A8D1F10F5677BC3C6E236F6D5827F70E3F471686F640A8253582F0E5FF146C3BCA53CAEF22BE026BE891BDB96697549DB993B9A4A1FB4B8034FA9EBE42F7A953155A050CE106EAA94C885E225F2FE61E254BECD43E34DE41385DEBB456CFB70FF8C0E46E19224D5FDB7A38F4D2BFA314DA933D8A93CC8ADB99344641DED33E8AAFE639FB38AA9026DBFA1F0C754192A57B8933C99181DF62C9F8101B86A743BF7D07924EBD6C14EF54CC8ED397D73AE2EAE7E2FB4D6456EE7C68FE6DC0912DF23414FC15575E7B84C161A5BF8CE69738211BA13614B1A40E7507701DF29D3B50C26C547C8E49537D10AACE95424520340A57F1B5B35CE62DC693694A2A68A95409D52DD0A0F167F4212826811993D4F245E56CC36A87C404C3218388947BA63814D3CD5DB8666EC2E2551E8E130E1A1C998F301005A645BFE2EF1D208412F3937246A5A41AA4F3D3A5FA2833B4A30637EA569EE3B2E6B72D90BCB4D79A7022824BD22FCE98905D2DBED895C069031D08B69E0B710B4472BE7BE2DB52898BFFAD6F65D73FD28C8BE578A152E53C9D2F04E206F9BD0714CDFC5FE8E17AE4CDFF12B9CB2EA5CD8CE7FE81B0033C9E1E91A88D3F475012BF6882F3D5A25C1E5BC92B89229D38CAF7314DCBA05636232E8409CEE11E7EA607FEA2FB2E2FDF07EA3994A3129DA426D64DE74186A1F8B432E106B786BBD302FE40889DF80A5B1CCFD129EE0B0DBA5C1D506A8CC69106D6BD5006D2BAF26D51DC9BE072E4BF5EE4D1DF9D7B4235CBC7DEA9813244122DFA753479CB66128753E5C4352D0CAACFC8CC577E2D689028025D72EB6A6BBEFD22F2CB01D5013D86A5F92A1AD6AF48F5B7C9F6AA8BDABC760CA25C0ED93986183F00CF3B9F37385DE183EB614D267DFD6E48C7AE1CFFBF6F977512C756103FBC5AA332F8647CE46D12CCD597A0D982C0CB30D7EC954515793CDB3CEB143DFEE2552ED21EA089A486CA6137CBE87DA4F27008B3758668DE776C46794303E316A3480ECDDC2CD5A97421EF9369CAF27E6AC5A56B7C9D42C64096EE13A1F8795042F24B92566021CAB8A988EA5E5BE324A19CF96BCBE0E5B1E32E932C7F45470D99D785BBEC8F9DA12EF268B330D43238637A16D699EC842CEA24E9BA3F2C58D642C9DC7C482FA73644B5E48B127573237D1F1821206081B7C34809F9839569EE2D2C650347B0EB48B76F1387CB79AC0C6306B9206A65E9C7B1DA55C153FC9D509F5B56AF7DFF6D3909F94E1F7FB57BBAF68555D2203B33F459B5C449113B559EB0272EA9C0D73469B72256C3D730E2752CF64D1AB0B47196FD8C07EC32D2E337242874397A0D5A37ECD4BFBC88C96B1655460FDD63B265D822A13DD20B8837F727BC1ACD58AC33603C73A214E90A59133A72B126240FDBB4BC450FCC19ED6E9FB9DBDA4CA3860A2EAB2CEE7D8A4534D2786695D6DF754F5EF52ADBF642C5F3A46C9A3CCF949A6E6B025856C07F801DEB544BD70C46FE86D4824246E56F171A81CDFD44730D55F89821A0BBFA449B9D0FBA039731AE81CF0612CD4316B0BAA743CB7E9DB42708D40C60A95973DC9CED8C5FEBCA232B093D7A5D6342325233187B9B2593288F6ECC0F163315598AE526861DEB3FE0A1DB1BD649D9550BFFFF90AE242DA3E2D67DB09C248C74C60EA83BBD56490E539C5D486EDD707CCF6C310C3E3A713A09183AD749D6FDCA2959AAF0B9FE73C2FF4373EEF3D4085951EB92ED015733D35DA818F48946648140A01951FA411FDE51792036B04A4A4CD3E5166DCAF152346FEEEE3F585F24532582CFE064DBADC33FF102A39BB4BC05247A3C78820B2639CE455866A344B557ED08172184BE286B0F8611236B68A9A97969795A6DF73F55D9D740B21C7D4832972EB79E033BB48F39B4560B0CA338D85577F76F38BD69626CFCF6969D40102291DA41BD67E7BCECA8BEE6D3AD58681630DDF7EEAE5A50F19AA0D1C353F36377D0628E0EB299F0464063AB8B1CB89CF512001949E1E66C68A750871AE0A3A5FA2108AB4E2920A08892D68719E092B48C3043325E5944F62D4098452B4A8CC63CC0792E256448F42C8DF5AB191A13DD00033600D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        public List<QTeslaKatVector> parse(String blockDelimField)
+            throws Exception
+        {
 
-        doTestKAT(QTESLASecurityCategory.PROVABLY_SECURE_I, pk, sk, seed, msg, sm);
-    }
+            List<QTeslaKatVector> vectors = new ArrayList<QTeslaKatVector>();
+            Map<String, String> extractedParameters = new HashMap<String, String>();
+            BufferedReader bin = new BufferedReader(new InputStreamReader(src));
+            Set<QTeslaKatVector> duplicateTrap = new HashSet<QTeslaKatVector>();
 
-    /*
-    # qTesla-p-III
-    */
-    public void testCatPIIIVector0()
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Arrays.concatenate(Hex.decode("14BE5C398A69D45B0E7A63672036E007981E846337FEE6A01789E7CAFDCD43B59471207B844CCA4240A56CF1DC37941B8669BB2E93B7A84687411425B04ADFDD81011B8DF309F9BC8069B5A0F53292F6F6D1A6B9191CFA8FE8DE932A1403296B3D6492B457849EF361481655174882C2A899482AC9236901E094BE6BE9113E9EB7B18E0018C46CAB21F14502C63553D383BDC53CA1064CE6BC6704EA05161F0C9071428340DD0342F66F0422AAB29950ED8A95363404759A8E08677223A08806814EBC824D40D89F7560175631533BCA8DF81DCED3828EA539BE87328C14183C91A6C24E496227C6F6A98197A8ECE46D5912D99F72D17651F5850901CF4C060B4D1E5C860C12FA479342652B54D5E5F58AC2449C6248483B900E8F7A2654DF3ECD76453450BC4162FD1AA9A36B6468C0E37A18F54A572E9E35C2047513466BE99BCBCAA678E0FBE657488CC110A27393BEC4841A4E7B784DAC3F840DA102438041AEC6BEC4AC261FE118994D38CC1461927D3C08AA4B3A22E2393FC49B5A5C408182FF943C1F59B143BF0F11420C78FC29162F9E2EB81277FD7DD8779F3FCC9FDB0E6065B1C41272709A716DC204C51B47533CE1B3BAE15EB050073E878F70696B92E6C20870E8F3C290CE4812E00C1341D4E36A9ED8BB974EF13A8CECD32F4D36A1E912CBC1A65458454BF58A874245EEEBB41F7FE6D30CAF412010D91F1D455F904459FC9E259C812D584EAAAA200395D23868E838DC7F78C1CFB23BAC26788C10A0DEC2AE358DF79980543B274C096D194D1750CCB74C0DCBE3C30B50B74C8FF33EC1C0C93B65747A01A85D56B17FC2F0CF091E7D98979028FC0151E3E3A3BAE05D0FA5BB9FB9B62F031E8DD9D597E5758DC1632B6040D72B0D65B1C566AB9F846C547C8809E678D86EAE55280F2E365C0B6539D3108763D7D6A3821992B9F0052152D8A4469CFA02B332DA1A41A340452D481660DDD8B2107FF8968113DAE0C4BDAEDB087B9868AC0E3DA8E8B7F54D5FA4EC26C94B18F1919E4E5181079A8C4DD2C2F6FB416D302FC298D943EB43A6B588EF9C9498036A0D59C85FB37FC4EA9C8084DA4C25193330359483EF8D9603B38E9A7107478762557A590D1DC1D07FCEFC9EC969F6811A92B69B184F8E605A489D0872232702464AC8616BF9EBD7ED82657541C3D96C3922B7E2C31A7722CA36B4DDA4CB775071AED78E2829241C9E20096BD64AA69FBB47571BAF11EF42918BC0606486AD5838EA8794B2519A5289C48517AABA1E51217C49718DE8040875D1E1049B0372A2C307E2EF6E43503314540D3C0D054ABFDC85FC8BB4E5B48FF071F0FCFE3BAD96E02108302EFECD21380FE59E1038D7E7D79EBFBC4DEB14CFD1114367EA1E64DD950A84B11E9C1815CA4AD3971EB20752EEF63E2F2A188277A93CA022C1AC3E0CDBE94110240F3D7FA1264385CA9B997BF24C086897D8B23E585D3BFA4F8B611C578F789BE382502A2A14D2CFD899842FC88B300D5E929DCF9388E9F60782DE92F344598901225B94B48C7CDDA04C5141A7296C3046173161A8B902FA9C830FAFD04CA53F62B11403E1D96906ECDDD190C77DDA4350F2FC8A33AF7C5C9BE9DB0C73F726F2730D27A32B85845032A20BFC5454E8C8E3A4501C7A04B5AE82140A65511CF68CBBA3A82F18448B84C0E576BDCC94C2482C08B95E03A66ED9D0206DAB74C32E160A16BCCE9F699AC3E9872BE87774168891C148314C1500B506B6272C87AFA7034899BA202E66DC224A60F824E9FF51C846D651C5FF5215F828093458D60BA1488FE84494A2D26F5BE5A484AE6B57CA2E5502C101A1C4C828A70AC1E7AD3D71CF8E5A4847A56F3D110D18FD501A0A60AF0950518ADD4008540799CC1D1D66DF821D92877B80A6C58780EC34B1C97CBE9ADDF79F923ACD142FBBDF39293A0ECB17F2424C2AEB5714710D2749DBC02351656F54B5268E114B549D1111CE4BA441974AC989AD24B5E1B07C2A74802F1C09014C752A898E0EE9D51485E370644988FC3DC0A531EB6609962AE8C583179833700E944BC002394A5A93F11B69D9F90E5EA56C139873241A8EA824C8ADA018A5A4247D8C46BCE6768FC310C2C4C3E6D3A91158A2F21B37AAE306860C60B890B4EA1F2A3ACBA3BA2D256031E0BC8683A41F454DD5E4929C6201C639A9C6BC26628ABF45B1540BB2F94D0D5D7515B8A553A0CBE3504F76EB5B5A3EA8FC6E115807303199A5ED93E1170F021CD9F48D5E7C260348754E4079926C69D6B911FCB5BA642A68D81477C4631A089037CF95C623C52BB59D697300E0919307E21CF9B0116C009FB38ED08380E2E3C26320A29BC2A01379E202BA115CEB0D61C299F71DD8E153AF3EBDEC1D0187836F6771FB71923CE6E1ED3265492E4728CCD62267B1D49F84200BC094769860C3409F5B07C55CA5A046BA1089CF0685B6BA6804335E8F196FBDCD01174EED8B35338C44CA3B666A5E7620C822E1D14449763FEBBA08840D446987A9308C02F307BF645F00A63888101535C3E256147BFE3A1B93992FA132B95CF3DA5CA76EBC1B1118D4469274628C047C647E76E69A21DA235B837F0EC5DB7A684E28FA38229704E38C012B82F5FC2329B7B8C32643D606C923D4D1CF945361A4E86F637107DD98DDE0767BE55991E42F422D7136CB12989434C2E2D73F9D6089AEAD7B002DA0C823877F102DAF2D827488BC5047F932828EC1598277A68C0A043EE333C25E8BA09B513B71053136C43AC637DE604A801B5D031053957F2AAFABE3B322466393C38B3E79FD3883D4B26952579C7FBF27BA3161BE47EC05C36274C486B28069B85F3A1CCC0729B67D4065CFCAF822314FE481A97410EECB6E629196CA2B4936FDABE460F9323C541220F6B81097DE9F2243598577EE8A931E02A7A4C99019C95A8D981B0DFD84E14FFD36651748B9B39358809587AA66ACE6F700727B16649545E3EF19A88C5840BD1993ABF60DAA5E75F3A8479EEEE400DACE2B9B56A010F5B704A45A9CF67123F3B72E00E3218B08D01EC3E62CA297B95777993D30E7ECEBB84E2C89F3008AABC787E2F463A0E8AD93708B441791389B447999044F2C297F300A09955F7FDE9312696728C4D57C41CD0D6DD171E1DC8ED13C71FEBDCC6E11FAF5661A4762AF1E481D290A99B714A5A9943210F686DDC15F52135DA1C324FE13AB500EF8990A7DA916D36704C0A2770172706F76559377B6B698235A2CA0950603E3664C2B18139E964F8A65EEDF920766960D7AF2C454EA459C8C8A8A0BAC144026EF3E07655E0468FDB0A070D02766ECE413F0CC6B0E8AB6141DCEC4B7176F747CA888BDE34072025FC5903954033E8C2E238C863DF874AE2352E69D6481D3EBA028CC82DAEED636307E25019613B69BFB3C3E789889F12120D6F82E6F147199D426448FB133DA20E2EA35916831652017E7D549BDCF3B2847457021F10F9EECAFE9D8F6B603AAB7C6D66204555BBA86C30F6411720BA1387C7420E39FAE827F454B851B1B6D7FF850338B9F6621F1C00D0160A61B1A5B2C1F416C6B3BF455A4C3E675E5C2CDFCE355C08513A06691497DD1A317DA84C14CC5FCBC8FF1F49DCD820117CD4E11FB545D5503BA4BD4B479A76FC62A30D3202E22643C4151874AD5A4394277246F72BD4F5D09C98112BAE11B037B00125E1BD1E20BA234DD5400A72A63342CDB726EC5AF987CA91330D77F3BD4CEAC2FFAD7579E8112C81758D9140FFD0AFAE0B6BB60CB78F40FDAD784F9085E2134F778FFA2FF71121D9D07E48DBF28864052D851831DF4EB89BD661882C4207E3F5685068D89D0DFD3F73C8072C946125A64B6EBF957C94D4A04409EF9B17FB5A1A7916C4610CBFED3474E563387B69793B3528650FD68C741E260A2CC5D2A229FB00CF4DADC7ABF168EF983F613F2B508A8C465171035D8FF0616BC319BD2B98C88C5728EF92086E4AD316247A199F6A123AFBAC048ACB1E0DAF62A0E17730674F61A801EFC2E6505E9E6029C862C29E79FBEDDEE69F8FCAB716723D6A99B8B091216259185A65C5A9461413EF8616B9102F0BB6FE68E014C62290345C2C89E77511D9746052A188A41AB3E1E141684B38020051EF0E4EE77C979A7B7EE08B91338ED742E30E91250741BE8D1EA6B4B105F428528506B646A027E82711C56D7503049021809C050E2FF3FF1001AEA49FAD40AF255713D431DD7BF6BB62B15B3F22EE53B4FECC7E865B3881A15488C18AC052C1DD90F21637D1E06034649DC8CED834F4A95C511AC19815C86BCE452CE0A9C15BA0B7E98E92280C9F8C7F2260CCE46687B46702F2EC81926B3A4BC9BF7C6E28247B5E248C231B84CCD95131D63DFCD33652D2C1B89025EC954F6C9987412F707846201BA2A07D80AEF7612E2CEA5B473980380736076583D140C71BAFF5C46023D03AFBEDF45132E16D02FA0152A0505DD9E20A4624CFCDB09737E08DD5FD1CF20C1896B7660163B564FCE0CC7B1339D74138550CCED7C7B950CAE55706EDAD6BFCF3C9447AD3D4D2CDE6B952702C0C1E1C3E3272959EC3F82233562959608B8DD7501D1BE022326331AC674D71C01883A216D8801880D14D2934A2BFF3087D3B4E28851C86180F474A4937DCCD901F3BADB1148946EB6FD8E50CAFA588749AA6987B2DB6EC838EA3712D09DF128C6D6810079C2EF26CF4F2082CC539A7B569455AD124E42347195C641B4291C6AE909119DA25E8C99B966B2EB3B009208971C159E4B03DEFD15052E53C398952988548F8D2470371D4142E80D074877E115735135478DA878947939BFB19D4B8410B7AE39B6EC7A7AB040BE4113848B806834DA4DFE8FBCA104D061D884C491DDBE44EF645DF53D89E468884C8692C8F5AFE3CAC08ABE231E721D93F76234604207D1B243FE4C9E69739D49E69ED4E68BB249EA4AE384A811B8005823BB893E8504DB7F0257A13F6DA14DFED71AC59FDB02603D11968F8631F4BC4F0548F51807F22CEB49A932AA154A9CB31318CB2C86413589177E97FB308092EFA08AAF8C4CF0316ABCC8BBB55DA9410721B5B90DFDE4682496CB040240B900A00BDF7D3E19796FF6378333D95D609EADF839E7E9BB05636C5B68DAB394E059844FDBB26DB9013F34FFAA96C88799A3B3CC8F192442A03FEA9F69E5E000D0B2E1EFD05B4D3953C63C0B71E7EED50A97F725542DB6B0D451C64971A9A600CDD5AA8EC6252E2AE8F73C16CADC0786CD970262780642FA8C320896980D596887B76476307A41820A008FBFDA15EE8126027B188092C4AEA08A734558DC1F680D8CE12C25F315421B7CB353C7D5363F06CCCB2806045E2663716188F439A9F654F8736D06BD0F98E1B53F5A5B1444F0CF43F1672DDCCA7AEB76ED2061124B5870C62C4AA66C03EE94DE1615627747D1D930A8DF250E9351FC8CB88E451FEAB0550DC351111F3DFBCA5B58F71A635F68F101F9FC5208084E75C4FD5E7C5664452A9733E481FEC874D17E85DF09FEC044B0ED303930F20600BCE3A9715654A13BC9194822E1008256B8482C6FDDA2E5A342D8CD120638152D48D7F3E25AA7589599B82E4AD175C5C4B09261337EA014AFF273F026F27019E2BC917CFDE737179BF8B8D9F563D4275F9BAA21D17AB16A5B8B93505F6C95BE36CD7277401EBAD6EFA0303678432227E125E6759270593C4903A7C8ABF8F7246E9A070A01C10BAE9168E261C1FCF8D8AE558A3050358F1D389445181A6F1BAE1685B66D1EA41B294CEDED9458325FEC7C0291762DD7CE6219B1D1615082C472CAEB7D616BFE1756622F212AFDAB6A0A597F03A7779ACAA95A4E385E36D4288A72123ED083BD8D9B939F6CA155E29451C81698B8236BAB70127029A258A6B0D6434F02CCB26A846457440990A72EC9EF332851197B6325E2D8499B8C70057F0C8BA6763404EACB43489E2F301B233FD66FE7D47B199A93CC355E1CFC350754D09104938A17F287C88E916AB3E45CE0A44987385E0E56B923BAC0D80EFE37779E570C20470B9B019A60C471D425437F8665B15D3287D02D4E93964778225A1428B579F4C68DA0B3BDE33C2A5014B34D93DA567BF52D11F530E032B0388F8964BBF09AA58FA6B64F4B463E2C3735DA10519312E98D5A55A01A326E6DCBA3F036AEC91206B089E3A1C19785C582263EF808860C79311A10E6E3F77C70FE96A0FE25C85C08A1CD43FF0D9A5447CC7601EBBB429D030AD2E28110258DAF68B9F90C92D4ADEBD0065CC9D1B090F57C93F8C773B913F8C150C6337FB6B32E4216105C91F13188A65E870DC3CCF8EBE18C19865D98645CC4CA866B04E55D8CCB25F6C3AB52DB299BF304D34EBD68C184D552C899DC57A03C783A46E01A8F82AAA35296930A687E6A99250641DC62E9A02D7CA5C96174198753BBE09C1A2A36F09B1A7CA42A787C16D2FB9BF739CCDEFED80CE40BE9EADB69DDEF02B6860A240E4A216A4D50A917D523C9CE43C26B56E4F87B7EAA8960283117D5B396989719476036E7C9DCCB3277192A502177F2FD5A24403C747D70E26B7D824988369517631223EC3B6D7233001020A54C2414C0C4FE3CF37057C59A4B2268197E17D097E24945D95508F23B10794DC904684E33203A6B074A466AFFB3AFA48A830EE6F1AE1DF35C703D5910AC3E71EC4805E402C2743D3BC9D824B02D0760525B5F738196A7E575D921331A61D2F5BD838A3A8A287E2718E538A22D57525AF21854A1B1F0C8044C756618ACAE9B294D5E36D84F720303600C8241CA2C117726CF6E5550DE534892A14115F7920491B9A54A84BE3C2A0E168D3E9ECF1BE3E7E7CFEC206CB22D97273A830514FAE42FF5B2D7FC1589E097217C16D658FFC1C08FA3FB37445B75A3AAEB0CFE91D234037DF9C824EFCB05034C1C2C89949032E90C760D038DDD894205EA12B1E6033F185AE335E0FFA129C19C435490DF53EC30C1B6B7261D1FB89E5427B8D0103BC8D8370ABC42A9B0CDC301D4B9827D27D2FFD361C93976234BC699108DB4B540EF89329CAC0933C1274487C80E4E8F128F54E5EDEC60A102585F408E890B52E590393318359EBCB710586261EDC1E66646B514660D006A2C126CBEFC21EC431D1E99D706BEAC7EC79D06A909D02AD7A694C672F13A16FEB9D51C3F11465A51512F14D23413CD10D7F9D47FAC45B751086E084CB1CBC39E64994C9696B0A3337B9A6E07151E85D91B46AB81E716798604FAEA1830C8EA55082C7F6206FC50ED18FA013934665F21233485597FB9767663E548E60A07ED62AB3A704D7950612032ACF4F811157B292F6FEA408F56CD1EFE1A6D3B8E616EBFE756F5055817F857073E69709B538CD7E863020FD0139FBA6AB3343B88E8A2C25EF716091E41F7B6022F3C06C148F2E819106CA03FA29A6B50E18BD16C347F7C3BBB4A331B98A4DDE7FFD3E6B7A70933844EF37A4F576B14FED215FA4747145CD5008CF90A996CCB7CCABE698566B97A4EF00B605792EF99E260CB9A5D86A0B153E6991A8720457A003003DB44D39EBD567FE9CCF84CCD6C59A765C272A15C683082282D3B103D556FC6160F4126FBC57FED0C183515D5C423229F1805BD2868E3AD8B901455CF8C7A1D2DD6225277C613ABCB6B6F3105C423D502669F729FFED2B857C7979C21CB74403024F3141534294AAF0C5B9C9E4ECD542BF31860009C24E4746D0A3ADB396B91F362F4C3FB5DEB7AEDE2B470487F2EACB3649264AF0C8154A3DC085497EB95DD60D4E6D8F82321BC84945FEE29CC232005E4506127A26FF094F11AB5E63FE9A9F328E817490907FB60DEC2CA22CB435F5562BE22A376A75C0965FF209C7CFEF14F3483D890FE0B659CAD716497C5377EFEE0BC2018581FE10A580A76BE0430A0015F4172DD42E1DB4684ECD7E43C6AB7F18F999591050F46FBC6E92E11A59167423B52D0F820267D604AD671732ABB111CC0AE8C4ED2899B070C7D3DC7D68DFA2154D6906964901414788D29C7F3501B61133E0DA145FD88B0487DA2B9466C988E30BFA9634D35646A81071D3FC599CFB0DFDAAC0D1EC9605BBFE4158E0F63D542728CE577AE346DED77D6C3202808E653E1D4F11461E764DE863E4D2FD87F715630B39C8DD867580572D8C4970AC640022B0CB82897D3779156ED9A10734CCA402EF088DAD77A12242F003453512399794C688E9347CE657424738B335C49D59E7AA2B422B327849484418CE24C45621E6C07D1D5E816D3A6D6A9A65DDB4D1BB04EAA72F37C7B4DB39EA71B99192D7587A5BBF51328412797E1902999C4579903A302F9831EDBEBB3795DD1D09B288CC1A257C4349EAF7E334E9B966C34699B6685BC80DFC1AA1A5DE83306195562AC896840516973777AF437EAADF751714892D84B63517E187731C34A11402204438CF587B8CC19DCF05C826049C0E3304C5A0E3029FC16D2F8B3DA03C2B169A6F6D6A44ED0F35826EBCE85BE7F4DBEBF1302CB042E0AFC3A349FFF98B50C8993FC24131B3DA02B5DC1155DF5FA3CF0B884D9F0892E26B1FD140BA03C4F877BB9C58A99FEB3322F26D5272CA6D000A83BADFE9585385078CAC90E7301F2AFB393147327793BD0A4DC2578846A4E7D69C0F5419D272F1F022DAAE4CF662E9CF59B972AE8A20FA21845A3A5009C23402EA65C2E3DE16209260909A6CAC11FC5A05F61EB20D2C888E3091E0044B80FEB0E3B96292E6AD661FA9B6F7ED9915538D42D44F182CF103956C59C3C6140606DC18F93BE449DC1E2F20986BE14D67EE1BD0003BB18181CB441D070D74EB4E26F9BAC32E64C1EEA97A26C88BD264FDF1A12EC31FD0553E66478425E453B82E0C13DA9DEDA7328C28C278765D1D3B022B2B5B3FB7CEE42C254811E85105D39ACE8E2B64F0BCCEF03571039B7FD9CC2886CDD057EEB9C8C5A47122FBC9B192843B861433113DCAED72885B1F8644A68D21444E59A4ED3E474B1753B4CE034E9E6DBDB4F41CA00BF45A8D1F0B229642082F87D266EA02A972C0D314276236CA5F2013E15092DE373C6F144BE76002843C2C0EE081EBA4A4C59A20FE826F4BD0FCA63C9C06B3DFAB222221D1BD4CBB603AEA2DBFBD6BC722CF479709788D3F518A6D0B542772208E0478F19D00276057D4D7A3C7C391D10C715333011096D1CB7A3646AE5168E3085FC987A0729FCE0D35808667C4F76C7FCB2410BDD80126E56A3C5C72522489D2593F95A862BD5803A0CB0A4843AFFF2400825D41F421B5E7E29D68737273A5645C2142CDA30AD09F0ACB3B720B9C28732C9291442F9502861197CABE46753797A57ED183983F4D940C80043702EB0068F74C514A3A9DEE904EC3854E91E6C33C5BE6770F57ED0540AED19DE64D3304B53EF5F0120E96A4DB66CB4FFC9B3FA79B8D29966AA2CD4522E84DB0DA115EEB79C1457341729C4E91768E92F0FDB0B7E2DC0725E6A98409A73C6400BA4180B4F00C8701090D34017B3C8CB3A6308BB44E843416FD6ECE1773FFDFCA953835945B67BFCCD563A47044F238F00E1CC82095B649BE6176F7A67F5FE8032EB8F155428C7C6635EEDE69176847AB40D2E60BAEB3018395A60BC3AE9AE32B06153A8AAB22FC2C944D8887E0B8A6BE6E3DD39CCE2E0C85C38B8EFD0A08B06F941A86E17800A88CC036021F9D023014C80DC9CA772079A2CB23EAC1F8D78B284D15CEDE7E5367546B1D71CC0332F09ED4BE7C9F0E62F5A80FF462D477AC30D282269A433E36508C6495BEBE3115ED517A24270F048216508D8C20887CF524AC40980E3766E028D553EB34AB7DEC4CB96A940DD15218AA17B8444B4934DE09F0C87D89923B7227F5AEBAE3AA6146B3BFA1A790B3C98CB8F8F711D349E5A524EA9DC5128862AB31F7AF61BE71CE9893044B0770877258685260D5E0AF6B69FF034212B72134B17F4BF2948BD12BB087E82416595A4E92AEFDC505956A449239927BA368220336CE8CE8928A1B997BEB69A42817C44C13CA0DE4FF65FCD100197338877DCA198D7C4DF1484AEECB4107833407751904A83BF8F39269A8EDC405E285E2854B84DA2B200371E126CEDA464B0600EBC9A89BA0A36EAA063F5F442440E669A48A76CC5F9A63105D06FB801B3968CB3BCF5C4C7263088A1712E1777870C5A3240A85500E3ED0B6BA588096C082D07049053DBC16DAAE9828F1B0576E5B003927AF8FD04CA0E38E7BD50FF1CFDB59E794F46520AE42270189176750DE56BC996AF0D47581A18CDFB469717E8861BE49807F38AE1ACCE1036B695D8F67AD2C7963951646EA6D792723894B70ED65368141A4C09CEC5A43812359E0ED2AF1FCB2198FB0C129D2E60EB7F3384460BF79A0F94FA20C97D602008D1408100D37B1563437DB470037C57E2E21D0C011A618E350C203CF459A3F8FE514236183A200BC90658AFEE37F0B94AB329A00361827B7C08D2F2A3B7153C9568426DC1D44D1E07EBF2BC07BCEFC31B5519690C4FD6758DC0AC5FE0E5CFBAF3641267503E0DFB689C4463567996A285FADB4D58F437A66455D5D38A648083FE6E5E0568A03274306D5BC413F1AF7DAAB5C283A22F0AE3498211EE4AD2680A3DE270F89F9F40A53329F91A6E0947244C6181C155A4E2E74C770A684B3B3F8C6E456F2E43F4EB2D0D3C5B07390D9E011E8D2D20E0842A84912185298022B627E87D7D04154960486150C9A721A750A035F601C701C7984296B71ED3680FEE28E5A47D4A78AF3142D4E034D26C431377C81D2201B87D52E57075A1D908A2E0200F9D16B602300A2316A19A1F9996A441D46BF4D1701335F742A8785CE099A948E1A8A12B867F8358BDB7FE440DCCC234A1A81C23D6CAD1D8129D815CD4B4E6073BAC8A78F6CCBE8CD0ABE28D65F309103DEB98F3FBAE877AFBB549D44E2399B8A07C4544985D9C88EA99C679F6177091ED64C374CBB54940771A59E4FB36D241F0764756F29428600961A0FB52E1E6B86012E74654B04337616067BA23B8BBB76C922C7B224614179F3199C007A7FCF534051B40A25DB8B20921C8DF46993D1D0BCD2D175DDB5F04905E9EA93C175754821DD2E07A38F06072A43ADA69A441EFE77AAC95327B869570970E8260F36401C4C7401077ACF0C2F7D3BC23B846BCE0D61F50911CDCFE1A11707565E4D52BCA7B78C61D96296D29B30E483E6C1EEA2534A783439E2D23740730092B170215427A78E079453565EFFA6EBD79EF6D03A39ABA3DC4C6165FA01DCC81E354FB986A09E543488C61F746F6BD7449666DA0D2A267C70C22B52E79E8A5270045EBDBC36A06E6F7AEB85635E91998F2DEC2D8F9FCDD5C955EE34CF4D4E039D59B9171AF084A7E1537C42C8A82B6FEBF3A528165DC465259E1CF7668F33A8D3E03927983AC29B1D052D4E12918A9FFB56C803A10ACDA79F00EE70967776D285CB7A1CC4D99F96B54B2B33429EAE0BF84FC914C182FFE66796DE6F06320B68B398DD92D757B80B4292141D6ABAD1A62394E0E5B71DE09588778741559E37A4FFC2780FCA13284C4BA15E0CA6CBBE2925C448D9D0C8C07699F30CDECC811707B4314C94CC6C2EB1A80346CB4338906CC013B532249260119E518227EE8149A7EB04C349CF4BADA1FE48D38B33B52648B59DB2A85F88C46B61BC37A3245C2B87566231A5AE26D645124993E8CB3FE1604C8CFE35737A654A248DAA21A20760DA210FFCC343B316FEA913658ADD411AE53EA837DA8B2E79DC92CAC6B4986543F5255206B807B9FBE07B56B3E211E337D71EBC1EF53A5C5E51EF044384995C87E1DEEB1DB610EAAF1A1EA866BC8B2E9560C2CCF522333E699E91C92CB4C9A615B967D2D4E992B1A8170428151AD7847849D713B41AF5D44802BECFA50F7DBCA626C5F60C5E9BAAAC67109570175944384E65AA6B624A73034F1C4459624A3D826E97848A0339417AC185B4DF9144E9EF9B691080E20B9BE8452D4633D106E65146EA35F7B12A9105A986FB8B68E31B301F8DB3ABB804244DC01C76E52C4C6972DFACDA999D48AF3561E849B10F00D06916172F27A490BFEF1E8D0FF3CB21F013409A7C1F2CE475B62CC5A86E527BBF369FBCF605BA881401DF460E0EDE1347FB49BBCCD01A0E12D7D97C771F30768E8AEDA387E3AE6E01615EB00F34734AC6DD320C8D97DDBC8F71D64438213D17147B9A0E188BF1C09D1686809F383384BB7101DCA45137C07632D5BF488013EA2D7FBEC4B2538B491081F90C47DA522D919EBCB87D83D941D06832A51C3D51F3439ABD64DB94EF540B86F627AFAB8779E0A52DA8ADC5285CD427965E223F2ECFBEEF8E41540381DA43D8A754B68F78D644A020BBFD295836E854B785CBC0B781CEA596D12ED21B0423368DF51A4252B0E2C4E73BA50841E36B8712A31166C935A4E41F6BBDDC2FD215496F1778C57C233C45B721069A5316D152F98689AE2A9383C9E7453B463F22E21BC78940944D9DF3301136FD829EA11E33A17B22505A2E0A34833E88B28650F67332D244E99587D8A528F891B1AA55A0A54FBE434E8815BB466D0CD0E2663892A0571775D1E513785CDC9938913309B15088C4D90A94C670CA65264791909EA3ACD5EEB929012DF6961400A53E0893EDBE432B0C595FB9CDC288523E70271AF36696A1D2E78DB6D7D3887A17F7C02D277185E7DA1521B7A45BA29AFB235048A403D86DAA6F9289F1612C440263A239254C219E695C402C56011D8AA75C163493B9BD37264A7F452208422988220A1D4A075AEF58E86A778149CB17D094BBDD10A61F94362295EB0D543411DCC6E5FDEED7F400CDD5EBC1BF8F7058B9FDFA71200157A10051D7E1753F92C14C0AC15198D82D7CAAC166B684F719E5F99730CC881A62AAE7351D0DA0C0052EE2D4BCD6D1AE038D0EF496F9F453DE414DBD5207EC64B5591291373D401E13A6AA04C4FF71D46E6657A6E888B9F480B340B160477CE7637816174BB060AF9039409DCC68950C74143080CA9B3BCCECE0DF5FD9A2F42FFD515C82606B324C997F0DB44653A83084A7C4EDB0A2E9D7C8630F5ECEE75313CC5FFB722A9F4FC45FCBEF0CA06384C22C1BD468A5CE5469369A50882254F79A63600B6D17360A318BD243050255A1B05921192D6A69211C26C68AFBEB94FB8C9CF9BE2068A5B0C9507BD2EFF6285473FC6A81AB96002E75792270E39EBC1C0258A63863E7CC9E58D8BF24D1E45B76E2C311F0F7420AB3156C4E7D5326045D861C3C2A25E344E41E11C61254462436A2C1D352990A3DA220E41B23017ABD92FD0C414C08C7C0F9754476D771EF6D542D09A61D025C34540BB18F143E80FCD41EA4A2B21E626E11AC84D1DFFCFA69DF901711FE916EEC4F8F0D131B7762F037BCCCB5DD818CF3292A17EBB0A83B02E8BE29827CB97C68025B6E36767762A854A334D07D46684A530332240BE0BA2B44F48FBDE026D1FD33D22650A2DA2331CAF89E4604A6ED8E262E31E23955D6660DDD5C028E841599E56210008F93D18EA746A8B0964037958B3F4CAB95CAE5201F664A68D2FBBFF221000AAFA138D8758FADD641ADD4931C261E218C7947BD7E79E7365E056BEF562A66299DFD2217C68F22568E8E64BBC228601E8D9915EEE3272E31DDB00F156539CC2C8964600608EC3D87CA3248EBB2CB5AE59C05C1294C22E5E2A22282BD7BC71AAEC0DE44D9FBF542A5B15429A95B6A1FA6EC76E171449EB682795F89D326227B05201652B89162C3035A368BF32DBEC955CD854DEED3E74B313ACA18508EBF16C8562A9E6CA624E1134589C3849B2E35AE1C569E736D7B6F11FF136AA6E61534D6EB96866139E7967102A095D4D9FA0B89817A156DAE43671818759A159BA1D3D894A7C9640091BF18B15B3419B0410A00ECBF11E70015AF7A3E0284547091BCFD92275FB2878FF78ABA5952741FE5EF76DCB5986B403945E9BBE3D681415F6FB106B684A88BBBE0E8311C1DBE95236846427F20583CE4F50C1E40750E074036EA7883184A4B1BF1C480141A082F62C112771B3C33517F2CD20B5E869F1D996BD7F62013F29BF8FD8C6A0324E1ED064D9B207D6A8434B104F4335806092B80EDF84D4A39B4173183A86317B77B0D1D13927251185A91896CC313775D466CCDF52250A6D36EDFB97419225F544BF0F0F179FD0E0EAACBF5DD87B76A31603BE31627EB12EF466EACAD5B3BDE7385F6AE1C4390D6248EB03A689DF78D4D63B53BCEE6117139ADF8AE3AD2249FD181F6554003999BC98F571B0B39930BAD526C1C0F1AF3F358B4F1228FF03FA69A8262C4AEA024B3E4984FC3DF5CA639410A70947216B0A82476E8BF4ADF0A52F18EBC0F79000B0854479349D5ADF2276C3FFDDB86B907D9A3E6F202CAE8178E20B656EA1B5FD4B686D6D4F9A5F106341A0CFB85FBFF11C1F285CE0C9C7C33322B28103406469689CA2AF8CF68D1F6226C08EA72F6813BD8D8A8B3FEED6D1CEE51CE8807781A4CA7125B8857A520FD126B663081642EA95E8CA9D76D1D2052CB60AB54C6CB9DA1C0AC094A46E1ECABD8AC28B4C93E76DFDA534975F36529BCB1B1827AEEC5F55C82CA9645AD172298FC2C1BFC38AA1066B2EE6AB32132058F34C5F17F66D0CE4863FE636EA2EF032C9B77696E37F1261C80E3AC7C7633393C68B29E809000A2C266A51FCF085B24DC4F55ED477A8878904D080E55471AB3CFA53E5730EE3AE85E52EEC2235BDA6267A18684FC1309F11C5E60FFCCD739107A649F2D760A4496E289CE4BE12A5AF1C16F53B4F88388447E13EE2B789E31F1DE44572A01A187F0D8A1D9FB3F4EB2A0938CFEEED42618AB5FC161AF9A89E54C476D24E30D6046864A3BE2829BDE851DDC11CCD255D5D961F3DDD1B8887D7D5642D2C787E9343B494A691E9474E86BA7D14A1B77DEBD1F6871960F48C42691789E718D69A58C94503ADE097B7C35AD8A44B1ECC51F60D4BC5FB08D5955C83B34FB8D90A2E2306791F7EE9FE820F204005746A79F6B34F33BB0D01AF35EDAB77BC3CAB56B3612F34574CCD94A9AFAFC6F12EFFD2B4BB5C3A540A052ABAFB5A58EB4A061BB575345A8546E1EB2E94AEA8569883A674206C6E3F370400D40E6A2582785FC2A62865FB96CC9F5588452D6210B0286F3C5B80DD613D8EBB934A53910F90AC21091B340243CBA168E42C481BC0E8A72414C9DE279581AFF1CE44BA05E7C04A8F0EBC9B1AC4539D2FBE519186173B0B8CAA9CFB39E75A9C3E172961CBD68D2ACAA37FD9FB238947CF34E220C06BCA10EABA0718B4EA77AC24E94985E7941A787108134F2AEB8596482671540E3C03A51F68F0CA1757E64F2F8159B285061C9BA19CF22B6D0C6E455D146824F3100B69E1914E4448200C330C05930D1F5457A34CE4EB08D0BCD5F1445455E259337E2E30920342009AF94E63B6ED85D399524EF3149C10F5C5A086F9FC26F2A9B273FC5915E73CF4A628BFD4513C7983024410EFF8A30C3380F2E50AAC7821F11DE55F9A3B2C81BD0F2CBBAD36E2B9C45FB28260CB8663A953C4E766918F1AFF791DBFA682E2786BF33F4CB170922E43200274A45D57E4F1FE8A2FC2ACD5591C903D4E0C8874700984F0B8D196568133E60BA5A3C4B1A4C688CF7184ED4E9C078E2B14A576842933F12B5779D606ECC64FCE28262AF818E70DD813CB32D9CFECAE0E4E62696E5C300459D1D71B3F39500097EA25A81FD840CB6BE80EB5AA72C8F04164221E77A12DDFCB980489E73D82D6F1C2024FD39AA14FF870E4CD3C8F2CEF2D1DB745AAC14E80B1DF21D7640872706511A39F300BDE1C215C3C4C040E00F579E852E3AD6D79A7CEDD8DF5C9F11FC0C459A3CDD0206EF21459767D98B558783F9880932FAB544B89BC844EABEE7C94308DE1334DCA8A2964812B76EFF74C44172B0665AD790CB8EF74A8A080E9C728BE030115CB39ACE40E44FEC1A95025C46937C7BB67013E4FD84B569EDEB29BF4028C7685C181229E5ADC18E72C2AF605043B42425CBB0A546E92B4F347115DDBF28BEDA859D46550B4F8531C873F960DA53B590AC354FAC61B124EA0880E4D728EA1DC61A7360DDB38EA0D08AC7A0063FF988065A66A709E03110812F8040B713AF9656D4C5B34460C2181772B1D7A57E188DD40ED6598E741B14B70B9EB14B6160DDF3C7B00A57B5B092405A4BC98C99EF51D22D06CAED110C7E8F09329793CCCC913CECA91E73EB605C08066D5E05FC5692B291BCB3B700C6310F2374DC515125E6B884D5167CD87F1A244C37CCF7BFB8F4BA23A516284B396EAB7E533E19F77FFF8C62684906637BB770B2001E786B2001CE07241A3A079319AAFB635120A7C029D3C70109B6DB7F9AB61E00DA79EE8EF06EF056F3628317E571C254F99078E09305854D6721E406926DA57994BB0FA93EB23D00D0EB1860FD33550DD43FC7F967850621691B313C799B1C196CDF53D5A65EF497133F589C3C96322843EE1404EFDF86609B764B9DD34064F6D2EECAE7697E75B004BD11079577D614A89B16E1AD58E010672BF8C2BC227AD9F1FC3F2B05243F0DB236F08804A42F63F663404772F85FC695591E69AA6019CD6B31024CC1C4264391E0C9D08EC4E896B59F6FA181FF6F477911E4C10EC6353C7131744E09842B0B9401DD2C5B89655339303A79C425C8C13547B9A29441119F682801DD11FA26AFDA02858A6F6421472C3200894060C9D671C4B7034A149E8C873E79B0800CE347B83E93EB5A42CF433488B16416819CBEBA38919B80A91968FCD32A00859D50D96F8F63651444BF0600E368C6B1870BAFFC845D8E4E59729A60995FF74805CE47D7A1EA847C174C1C130F20B18290C9C4CE8764D66DABF4FFA83D2F979ED313474B5934D22125E847147F65A76D106CE89225AD2975582CBC4C1DB1108EC59964CE839560FB924D7722D5F177AD6C37A3F116520127C88905F2994F70DB17A5EFF068F45CC3362F467191FFE4A42129A6404FDF61C73278DF985388FA20265B01EC5890405CFE6AED7748FB68226FB6247B3E989028DA71690B1F7211EAD7C1A5B5EE2915121D145CF2B3F2132D49547FAF752A157434C3DB8F69386B55F4354743C093EB7B996C4EC3B4E046158D7C937321178864499FFF8C890C286239E51F480DA10F0DD0F38CA84D4DFBCE2394F981AF92C93ECF3AAEE468E0E4D3A0EA1A33DF4EB95112A9482FF323204A9C230D9DAE43E18A6D0F290FC174E93EBCA3D3DE2CDC14DE43001106CC04783BEF86A45BD596C7138686E4C09233B78E19F7193F3135F49BC89E43386F2C1DB34215FB6918C9DDA989A760101BBD2E010DFBD4AC20FE04F676F46CF4255236F52108812D97D3CC64AC48B4DEB7BE327F61AAA4D52C6AC2BCCFB2ED8A894F7523C7F2F84887D0A310C9A57824831B6E09A31037F093081E97A4401FA23221B415F7B2853832A861AC5692B2921BD4A829D8C46176CB0D9AFA4E2A613B6ABEF0C421562D74AB6B7EC530C4CAF36F4AC70D4A6BC48C998D830E6AA167AA9E92A8975968FADEE4A8431556FEFA5CD8216005A225605B24DC095568166BB728401AD5418C062F81F83B6EEA6AD9DA335DC07B84CAA1C75525108640A1CF2A27DE5DCACC1741E7A627AD640A18B8783CE7E371C23968A8205AC7EFC4A6844DFED2A8FE126266A41E67494887EABAF60C3FC744C2FCFE09239FCFE91A6C034CC1FA5240858BB0174736387BE896EDFC6606FA39ADA03E886E5F660F9BA3688C07ECEB02D0944910DEB1B5ED6C65699E8DF4C973F2E0131D28AC2B9DCA39D10A7DB6050FC4C2111C41EF1967382EEFAE507E88553E2DB6AB50358600AD0FE8A0A4B00C81117CEFA33F93AC918DB4F758BD8B59F05265CE533B9FC67316C739922DB96783745F531A663DF80C703F5CE7C34AD57165583E219693291058AFD0C1EBC180E89F7A47E5B15CE41E1A70D6D43D2F6B60DD7605EB50C7E3AFF67CA9BD348D9790291F5C4D30D0DA8DE07FE303C576A5B39A39E55C615F83348DACA47264CDF4136037569D2CF0484E52C461AC8D22E7F91250B9B306ACE71DED5637263955538D04AC316123807C9286ABBD14538223CF2A446ED8110615280F015EB0330C3E9AA77DD7C11938F434CE346943E06913AE4CE6CE112B4F6DD95124F4233BD27E897C05C441A6F36DB3B565212FC1A8E9771C968E826CC1B9E6AA4AF5F3ACCBF6518B38FF4026FD247158505CC109F60CC4D4DFEDB0E9B885309B5DB3F48CA00128EEB4CCBB8AFA45758CAE49122DC109DFB171F4A85D40460BCFCBA5A9487196420DC858EDA59E30C52F2883A5DA570099CF4C225DA6500D869AE955BE49FA22C62AE7C710974F1236F59AC43222114F9AD6E6E9945309FEC819D23E146D8F643C61BA50291C6701920DE883970502641CED2F342D13E059A5061F1DD117961AA5ADBB190AD4BD5781D2AF9992CF3A09E52FD2E867FB180665C0A059637328B9FE2F4560216034104E1BB00E5C82C46B274291C0EA50320214BC228398C27AD8F8C82D746083A04F934E4DC2C0B7070710803C1F9B9084CA91ACDF2DC0A34B53EF39143E88835A104487CEAF380A7C00DACE48A3728D2048438FD3EC255D68A566DA4180490A3CCD5788603CDD559606EC5EFB8861CC1CDC5BA49301BD5163498B37DA0CFD8A8E585380F7237A319AD7F1EA1C32BD49C561451A17EDFA47485BE0BE077725C9560A0C262A8B534A9716483123BA790CDD9094BEFCEAEB7C093E571198796C4A21517523B2D091E2FCF42AD241ACA64D980CA44FDA91098E0F688E3A081706D43D460CE9403236AED42BB55DF1D5672DE8871CCE42464920E98D316F7F4FDBF930A1F0D828127B1931965B499257A8E8533C6CA30CCF312EC468E0A70004A45B073A163A3B943159552D6DEC090C822BC8093A467C7831818D6FB5CACA20347AF117E79F555348EAA278F1493AD684C973AE1B5B1DD1BEB92A771890E43E078F08300BE9B3394B18B1CAA976A034830C43679C3D831C416DF4C3334A9EA6253630131451AC729609BB45AF9AD6FF27E570EA438E33787F29040AFD12A7B1C770FC39A9274B3605419B4A641FB61AD1442B223C8A7C58980BAF33864C87B1C41E25200D880B4D26CAB5BCB3B5460A6940A222986672CF947B473CAF496386F510EBC49532C8993F2856DF67FF8205E3C0F9104D997906D24CEC47FF9339760C4AD1191D192F145188E4683F8B41C7E7A43BB118B44384622F2281C6C0CF74B849A673357A768D524C0D5C57681230B8B681FEB2C027DCCDA83ED450E7B6726436BE85BB645B16FD8DE7D67FE20ECC380AC5459F878AA0E8D418527882EA23F6431941367CF8BA6F9E763CFFDD811BFC4F7C5D4FE2CC8F7E31A172B1503F0B1DFC4271CEB480983E6B1E42F36539D1E6AFCF646890AC4924063565EAA55ACFFD9ED231A68BC0CC1E198C05C4B3A98E8612DA8740E09F92D488EC9201D4EE6DB698437A8AD8D393645AC72B20270FCE1C139984E4D790387BC7AB8F8960ACD6A6A79D07102DEE1488124CB28B382AC997FCACE34785E95768BA9273D7B8F4A1BFD64B76049464656D7C3467CA9441F99AF5E8DAA0B0DD66F2C8EB2BE46FDA405EC84C9B013F7063C38920083B1B54A183A313B3DD1AFB62DA777F180EDED072209104C0238C12FCB0A0FCB45CC7179FCC7B9516FC787BD0A8B235BA225C7EB8DF92BD14D2E1880AFD08C68FA9E43462F2E8465E8299C25F0ED4C9457E0C42BEA4F13465A0922A8C3BF445CA8DEBC442F6FF8142AC2D2F192152F97B0E79C44A4A42B57C23152693A31EF631286FAA44E51729A28D63784CC425DD30B5FA97781D575FA125C16DB0CAAC298C6E9DC57F38DF5BE47C26102D326A64CAB4C0932F9B909A3C3EF473CBEA1FAEA6BF97DC7EC0F1AAB05F855E8DC4CADD0B9473416BA30D0D52CBA5D45778DEC65172CC0782FA328FE58007529EAA28AD270F69117C062CE48636DD7F8B56012E626033FC0928BD4E016E5905D22E34ED7B0FB5622B1B0889F1033F3939A18B15F79FFF981A8AC831AB36C70685A641F759B6663B1F1E7A25119B6BFA2696B5109D0F82E2C685F51148CF94D6C993BE07872BC40E0538C74F7C0DC9C32CF6F9D887DBFF48168DF7EEC618484894850734BE482D7AE8AC92E5989943489523E1AAF9A8055D1509CDB3409EC497A472E51DCC8417818768A8CB2C2AB08334FB2BD14A8AB091BA153AFE1EDD671C14ADC068A31980D1D66097C2AFF335C68097EAF985892CBB52B9CE86C9A09A0D839F2904BD9FECCB2C7AF44041634B014CE0013520E84E63EEDA9BACD8D562031B9696C285CB5E9B644263F1A0F5553B0BEBAF708577ED5ADCA02C235B1E1D1168249D14AB0C9744DEAF03A4E15880837C13BB9D5344EC101434835A21AB034529FC496A4838FCCDE9D48501DBD9A3BC1868B80207EDEE6B67A883324A243B9314480C1F5F9096A5B147B87C4672914C2FF585DE510A194F730B33308BDB063DCD924FD4207083FF23C19739332632F899E0818F68960712EC0B41DDD220706F9A16B0757E6B6C759BCF54A9F42A3C212AEB60FA3881E6D26CCD92912F2F877C5FACE9640A6EF1DF1BB453CA111965B75E479CEA035D963EC59062B871834F8448593AA5B0951BFC927B495CFA796F11079C0047F3DE6823DA6E2C01414E0B55C3E949734678E6116A91F0C9B181251D18B02F515D07AAF37709A6308AEC77829D0411020C3BC10278499E947F8EC8EEA659CF1A5618208B01C2DE8ACF88C6AE4F45669FF6A541CE73132D8F87DA60A4C99AC33189CB9532E77A0B0027D11F2C3D709F972FB82E552F1C70B1CDD683BE63746DBDE3E82E27A91940FA80528CFE358741896361D783D92B110936397085CE25198E4C0EDDAEF80DD80FBC2B4D45F77C763BEFADB2FDD37E6258C2ADDB563D716F8CAE3D7352C863AC96AF178642E3C0115FC515D8397212BC4ACA838A01ED3448AB7524A611937437A73863B8407DE0C925D291F227C3ECD98169FE7A7A00A89A8477B083E0BA548B5D81AF84EF7A0F58E571C305D26A0D1A9B175AA9A0BC00C6DEA795A42E3046BDF4B26C89B3BA4402E983B6FACE5B0067460EA8E12494EAE0A34C5642902695815AC8DA4A5DD7D4C023CE3F32231B6B743CA9DFFB34E5814B99350A5028C0E012754FFA7F8EC955201091C41268E88860A26075138CFB8398F540746ECB74FB8F45233A7BA9398B36B372C7B5E4C60D5006B21A0BEF8D443C66CE4398F31A2150FA53ACD8B91E2A5B1A71C3108B849F4EBA1B2018DCCB7A041CAD0492A5B2B7D31509C411FA6D424CC248B80412423EDB3BE697C016D42C2500CAC819A4003B2E588020EF0EF8E8F7B0A432060881C81055FA2F573F1DD0A3030327869F704FC1795C6B9B4F9038AAF1771A0F83962D6BF12340B8DB0E49B8086A6051BB1EE112C0534229043ABCA1262877BC858AD98936D71D1004EB193244201DB0D014947141D0C42A1696283B02E5D23722696B1D092220874543405356BAC809B34E50EF1614202F48A3F78A82D8A885792CAA4B77F9CF09BF3715D0A25C1C604CC5266BBB1405EFDBEB4F21D80E9F2716B652763A2E87E854E174BEBC2D52C610B8B707D8419E01211440B86A9F2625E6C0FEB16D2ECF45843867132BDA4C3433F8BD6269FAAEFD21E5B101472700FA6DBB5D28E9B0F7936BDD220C036F04159DD3888C4BC8F017FF020183B8DB45822F3303E3DE3188DC6351D6BAF425AD15D47CC3095ACC6B747015A821AAF5DD10407DEB3337CCB403C0E94133994E989F785A642CB90794E91B8F36BC520FD74BC727582649AA698BE7F640982F17FE1C64CF07E51D1E307B16C8765D9596DBEFE0A1E3EC39234BADFA6D58214A91FA01900DE41BA8E22BB49456E61700D0A38D97C4E7C4A23A574866D481A9C606FC83A2D117932B73B2B541A1527DB1DDCD52B6ABEB81A8BC52C4D9CAC3081804264CCF66F775E0258510DC4045CB802CC8A111FD18EBDC79F661B929E33E347AD48562FA5579BB35D694C759EE500645842718DC91A2B9FEB4D72F4624BDB4D5EB5E02529F3E03F51917503FE008B585D03DB4BC1BF29F6590DC37DA1F445A282E44A3C3836708E3014D5516852E1276A2815895639D31DC3F95C22969FCCD643182F83EA2607BDA84B2E84252B7004839C1C2B5AF1803319A2CF87458726E748AB78B35D12C15FBC0BC84ECF20EB85098508307210AF120B4E1B4ECA176FC605FC13FDD73486D00A9FC6996DCDE6139C10DDF4DC868E79215D588B35100D03044AF5838538B093A6E78B5070EFEA4038397708AEB21DE1B81872947196E24B9280B4387D1F03DE22E21309C58C3E108A4E5E4D408DE170494304325388721724E8C5251C233869F80F6E7A9692720DF0C3C5C8B8C21D5CD177E9F47F722D96819C98785B1CAFB87EA6B0269F6B3641209523E643CD045F31A980DD2323BADA84E14FE9289F48617B95D0ACBAE4C6083EC9F5C092013D32658DD482039ACE8CBE64557C9BC742D29E0531F6FAC091738772836DB264852FDF89788513F2F5D6378D5CFAA62BA68F7A119981BE1C014886C0644DD2F28E469E3395DCAE1D4563122A4D2B2B0ED14A35C66873970A1DEEEFECA97BE750C08C514D05520DD7C89F5C588068FBF21229A6F86ADBC0AC79272E102C4055EEF9590CA0FC907D44C2466F3E075DED383772A1E894C244E2AEAED44766B9C910EBB858B169C8C8B70621156D0EC12833566E0C8E7441FA68918D6C91A0471A0C8D6E99299DDC6CE636ED2A0698898AC65DE14C04D505EE658FCCB3AD4346B1CEEED208940EF0EE2F3A1719BD6F0C8DF1F91756C47FA5843CA3351DD12509670605CE2394EDC2A19071C89CE827991DDEC83631422487B3AFA34876F1C0424B34753CEC2E82B76D02CF73828014DA48885CD6098707728C75B1C3FE23EB47633CB724C098D937176C07FBAF6C4909945BDDD62D068D70D54B2DE5C2474923042C7B4C32C8FC63A2BA2D41453154"), Hex.decode("CA09DF7F89B9B339C8D506B4E0E73FF145F5F87FE26354532E7EED21C6FE3355A737BFA0264CF661B9CE14128695D598254BAF3EC5C44273B375B70FE7E6457358C2459C1C19D589DFCD9895498E28A94D7BCEB9EB4C1CD113012AA3719165B7CD0507DCEB55F284CE537E82F60C0F83834833D02B6D825DA2AF1BCC6007B13F4F3666F02248A83843DE6ABAC5CC4A68608C261AE7CF5D8809956C9EA249AB005D0571D7399F7E51E5C730A15AB556E87C08067EC122998E0B4EA0C7D51B681FC82A8640333401C37179007B3DF4648D221CF2601F0B4223619F7474682A2607AF01FC5E6D40BD78E10982EF070C16706093A2B408583277D42D76DCE0D2607695A48254E8533E48DDBEFA39AA25060FF3BDB9B234964DC82B662DEA346505375A42A1405A78F294D4402ECA92C725F243E2B9D0C2974C05D45BCA30B97072CED73B30837F13EC7FC0F80000D26DD73D5E90F707C60157A7CBEC0749DA50CA136D517D1445FCFE770711F172854881BA302EC27A960F2CAB2BDE5B0631D2CA040CCB2850E85A868E0217384A46F266141EB354413F08D1EECD054632846123B83C9C15A03D83D9446A6127273F943538D60B8D7E98C9A9703CA87605F3BAA03D8BE3049F05394059DE0141541F3F08C298D05B0C3030CC6984077D5CDF9AE9D42DB1FB342CBEBC411A53A51438984B3B4CE07B272FB6D2FF5680E0BB6325CB137A3F26A6C86E5715B3C3E41A84DE438A94754DB0A19A1D18CF587680FFA2C435454C148040DE321E13E31929B348810F95852E0B6855F8E31E227C71C1062C8C61EC404A1F4F2CF73EBF86E92050368794E2388F5C58D171251BE631CCE534FF0437675B630A2473AA239F1ECA4E641369E9C67382D41A5111BDE590202A2336CA8FDB8132767C0D59E3081586C665802899275448463FF284AC49205500ADE4E93F7040725399509BB692C8EA04A092311030594C51730517D3C96D79EF3014AC404595D70E764F83D5B871C8678C0819239D3D8D63B1E29515E4AC731CCD93421EE6FC3AE78F520F40DBEB6B74ABFBD4355923B9F66E2ECDC39832420D7C575B89C82F3679EED6460D6249AB4CE037E8C835F18406ACF8CAB6DA80333B1B8201E0AAD1B3C8DFD828D1CF50B60FA5672F7091ABA26A21996B27A0ADB819F1B03785C83D004F8E000D8FA81956A378DBBC03154F766D536B1EFE75BB1C3BE660209E5D1E4ED205DE0B856138B57451DFEB1592ABC9625D8352738D50A1D1BA0ECE4CB4854CC187C7F35467879294D66B95F022C1ACCE296CF76D780770EC9CEEC099111BEC60E6BCB031D45318156BD59AD2F05A0264282C167326E07ABBD40D924D8A3C314D586B5F4423C081EA3EB8F9D4F85240106C55074BF1B38D335FDAC2BBEDFC6F61F02DD6F5472DDAB4CB5F84C09E804C35E35AC9637428D7BBFC889D32E78E2E0174F792D66C2940FFF090108D26E2FAAE166A92538B761C1C8FBDF4882102C2F607ADF275C20943D16527BD748A83B04BB8FD5018C5C5DF0A985644CEEBE3C200741EE8A39BD6247C662D7C1A24B500F08BF11E8E460203BCA47406D48D82502A41DEF1DC7FC9295E193FAE94057244B0F786F7A24E454699432CFE2F3F7472C63FC21131C7B9F6DF8BCE2D930CE65FCA9014A732D7BDED658D1A6CC154D7D995A9B1CBCA84DF26A2B65B080E462D1799E85575413D61A38C260E25B69E9C3A039BEE3395FC1DAE10B2C158CDDF4319C347074D89B27848AA6B05A13ACD6C51187313FDA50882E6B83B30CFB2319B4F3B3F5AC3A95B0E8A5C9B85E60E79AB7483DAA8B4769C14B75E28923839BC9A50AC8B2D424624A9F3A1B531F0921330B7B9D97C839AD64B1D2F9DA93B3A8E1A2C9A72F0D647C8D2E5004164B18130C39F54678E73E22F716CFA467918E440A159A8514A40ED2F0DA6873B9952F5D25414D2B2F4C21FFF321EEC20AB81E24B106AC33C648E96AFF7F293B7A8D676E21CFFF481EEF83F61674398AA01669004B5F7F86E54A7AFA6301392BABACECB2C18E0D14039514F982FFA26E024B5927E6DDCCCED9FEDB07A3FABA8C6445D0D63099832B133089E79388D62EF391D6CC089A742662A5898D7314C901D71004ED6C6D85B4E4561AFB8AC3CA25B1179474CA69D0E7AD185D9F1A3D8D066F8399BECBA4D430906446C3622F920826437E642CEFD375783C7D5A2A31326C0634F2A88422C456065D8BB515D818FF1ECD30FFD6A377103E933C086BAA9A12C068302E281739709008E2CFE66186A0471501082D1417EED5D233301683D2939B3D4C8A92FD5C4C3B28182FED67CFDA6A0D61269E2E123457BF8E436F57224C7F8BF7B63718DAA975C0681DB31508AC84634FFD75E55ECF9EBF27A59BD3D06BB909077D16C1A8C82F6340301FBECA553183CD09D72C320B97F1EF43C96F7DCFE0076DDAFC532C6057EE06C16120AC0145CD02DFD64106AC7AFF4F555E0ECC0CB244A21919E8EC955C18D320BE0B336FCA69D3FCFEBE59CD11D23D048102CB13879DF6C1A325B97534C5C4720411D490458ED1C5C04342E25CA0C94C2ADD754F672F80FD2E3EE424F7891AB751F27259EA65889B92FFA7C04B24D4076166ABA495F969944E6C41A3815AFA860B1465BAAF4A0FB2E139D366980B47E9F72D723A00D4D26AF88248DA3494B939D70352131D3D994E2E988E25CFA3DA83A53CA9B4215CF894AE103770BD04A4C75CBB423829D82E959D29BF84E83C5280DA95E27B5EBB45767C5CBE425A6B431FBD9EE12D694AC45268E81C26716255162288A20622ECFAE30724329A136D714DEF2B8AF842831F3472BD071414EAF694A8524369E97FAE312ACA6498DF1834CE9D8F96D1265AA59EA68124C32F75CB8C1259DED4D7FD0F5212DA0B2828B74FFF17D4FE475D81A9BF87C5F539A934025C6E07D853C2DC58A4AF75461D94BB5C32FD4A98D37F1DC90418F3F1EA9220CF6807B505A503F628A8969751161254226A93F20E1D042748E873296C1492D1ED5FC9AB7A8EECAFF71F114A49C8C192971AAFDE226C93EA168065FB0F03703D3B24247DCC7293AD97C1710CCAEA9FBA6FAC896A8ED9449AA16A76FB53B20C0C80EBC53F109F6E37582ACB14C916068B288052CA8389F456401D84FB3117552969A693669526E62D48ADCF24B2BC60381BDC3B138B25E2832409800FF7AD3009DECA801F408C8B251EC39BB4DCE6179119563F6FB451E43F93A83DA917C81E5C77809243F212865AE369996D62660F3AB34BCEE7F6FD8D1A23B0A8318211D16F4AEBD258B70F3C7E2D8DC69F2B52CCA0B7EE70B10B6C2D910A62BDB02160582C0641043A84F9AE710DB30E718245111CE2DCB00B82B1DA1D29F5D059BCF062DBFCB5DA4674E14947E8138B127D2DD201C4859035E4E92FE7B7717E971E3801827B7436E7FBC790791A2010A8232BA0BF3129B9C54BE3DE5DE4DA1D4CF6347D8287671C65E91E5BD86C5E69AC636C25511F6226DB92A5B6E92272E3342D3B605E2BAEA289121B14B19A4DF74D4941B24DBA840330BFFEF87F906BEC30A48AE50C9721CABDD5F12ADAA1D33A0391D7FE225BD111FC4A85447248C6033D9B531CB387FA3B29C70A4314F0C88DFD8070529BA8A52033B4379ADA6E37263D6301CADD83A9900ED804A628D98772577DCD8D8BFCFA64814983F96A90F9536E68BC05869EF1A1F99FC90EAE48E55066AEEEF9CB156142007037BADEF631DF535340188F5F2E7A415EB79889E6283256F4223FCC58D484D75F987EE3BF546C6070053C66D779C0F210925E30A152E80B19ED8FBBFDDE55801CA44E435481E27C5E04F27C57DC147E026290510BC09331B0EDFFFDBCD2A66AD13898E223818F4806196DA24F9B91AFC4E9B571581E0002DD6BF91331F3A640952ECE5DD244FE1474838595D8308B22DB80D560F4E37D3954859C074456446851D91E9E99E53FE2C3D9D0BF95A36C882092C40060AD0884D17295F7D454397B515FEBAB538C3BDA2BC4C1F93A0CCAF2C77E400347A6A31A1DBCEB24F008318957CE9D399E4ADD2A5844D2FCCDEA92D5CD3789F624AB34F2A387D600BACD5443AA18DEBF5096228F6F249C6632676ABD554875E7C3E8DCCC89F00B16343E164BF643825FC40DCBFD909A188B93985951D3D281441202D3D75C27CBAB4D20A5CCA82403FD5A4AD92ECE876177CD2C7B140A479BD55E9066BE4BF1AC453AD389C3311DC40311C590241B6B612BCEDF7987B8CFD11D65D3F249C9D7C638445C6092243099681F502303AA7766C7D1314F575BDDB3F388AEBDE83A6C1514F4C881C3698AC89BDA978BB9B04637BE90F1867F7126CF5EB458F681016556BC551514BC8B90ECF78DCB2914BBC40A925D2401A07AA6D1AE6085D710106B6433A1CD1A34C4CB1A752CBA1536E5C30279E7775DCA27ABC86EF7A26067C801631AD6BFD286AECCFBAB22532F51EF33ACA53FF8F80AB55CE2C0502F798FE4E727BCCD8B99364DA5417E24836F97B98EF05878C9CAD1CCB42A1443023173FD11EA32C5570F031686AC51C0FB9259336892E863EFBD7AE5691C69046CA1A6B11D7D16D528A89864258131F85F0C0A9F51F07DC3A8FC3ADE3B817D2DB1769745E4E59FD5AA1DC7F1C6422C7A51C23846DD21429AC07DB6769923B4650F9DCD06D25DC524912DA42567BFCFB1A9A0EA98BA0526C6BE7FCBC86CED7CBCAC9AB6CFC261675187DE67B3950861F58E47E0742D8F5CF35CCEE41622D0584F89F0C829F8191A2D33D68A64F518E2F50D44BD616D721DEC74E6597AAB4002FC87DBD9679014F9A887715CB2515559FD80946B41A4D27490AB25560E4A1FADA6834CF2D543EB9DE5378D07842BB8450104D39342E21B522E3A0FAAFC9D0771AC27B1706AF93B13C8AEBCC8D46EF5392760967A3B31E4900D5A4CB080B793558A56C1F1C4CDC41FEB54E85FA8444C7DCD7C364D8411EA0ABBCAEC8FC06EA482B59987226B025B6B455C31A1FDA8F6BCC1F00E5D2E9B605B330EA0A407B58AC110DCF1E7476A9DF332752EC63D50DAC7B4380D1ED52F080B060F79F2F145D318E9A09BA567B9863C23D943D58B3EE7B2A07D80402D11F1FB0B4CFEA9170AB3430AF016E08DA6A1AE4DE84E0BEF97C43FA2FDAA6EF9336E8F7D18D7DD0B953D832BE2C4FC05498BD8EFF0BDA01B065A0D0CE54E6C580031A79F19C1536BB261E43028AD8A0A4D4AADA0D5E7C498960289F462672AE9AE4C2A11973D0DC5A66F628BCCBC580D0D507874D01A68BC2720F82847FAD98E5BC13444663B246F5C269146F64A0F4FD6FC1F4AB9105C19A92566F8F341410C7788164A97D8ABF531C2860DB9004950BE0A92EE7E5D7B88D20183479419B5B2520AEE1EDDC951A4E6D4290BA2387A003609E5C826C470EE476F9D0109826F16415B0B746D87D093A006022A6F38E4EFC21548DFE5030C7BB45248067F5F9904A8FB874725D5BB4137D2E427C170845CC2C8DB4F20FB0A6BBF368AA9C4F206FEC9A4A567E65060DC133DCEE08DFC67656EB917BBBF1CFDBD1F315D71A75E8754A4AA9AB6D35BC618CFFB2DF03E796CC00D687CA807BB974CB1B298F1D31ED2FFE0D273460BF10BC99FDFF602C619E6A42EC55817C0CE483598EBFD917DD06451E96466DFA089D67EE1157F2C9D7C409EE5B50C048DE8BD31F0F297774B226661645719A8B8045EB2912820780A49A7C5EC24CAB39A218FF6C74576C5810B1788417CA180552BDDD82F2ADC38CF91192E2F268EF78827DA8A5754A6AC6D2F860EAA20A863E54694DB6C27D2DB37CAB532319F64804A5CA5C311A26BE6699830AE8B4B2DD67980CE9B51D828F6B1887B81681014C9874C50E043F2E0A8D7D36B914A6EAC8E0DD581384CD66E87A43760C838896AF808C33122356AB7C0680249EAD3A291C90883EDD4BA914DEE43DF13E1A70844C08326882A818CA5BD068C58AC34BA919D25EDCE6211866772FCEC1B985D651BD472DDAE6B03A6A9E0BA00DDB3FA2D62B07B82E05A3A7A7933CF0FB8EC22399B3EC77849B8D53BD7781971352EC1359951E8D4FD06244BBC2C6EA645BC37C491DE1C2EFCB1A88D903960EFA4C461CA21B4059164012537260B060652BD9ACC36D0223D575866530A9FD7DE11DCF18FE44BB84C0C6205D77196F349DC914736AB83CB87506989F83A627F3DE150523C1BEEFD9528489017C023B6738516692CE73CB32E13AA3B902DD2D0A7908760D12D6B170E3D04048ADCD8B8DF55FD18082F911F87E93113E1A66611F176AA53DC6EA272D6B76115AA6E210B9F1C23EB8E06EE521A3806917D584ED58CAA992C340660725EABA1DEA69C5AB8F05395B7B0BA8F414BC63E6FD42AF6AAF2A5CBC0AB15D57E7211E02A8344056E53A665F20EB760237A5CFF3547F84F4098936C4C05BAAAA6B6D7D64F07F9A047CE77E5B2E6234ACD3279386C6FDD5233141F8A799D62DA14D03869C6DCEF12883DA4A846FA286C8D5B09522619FA037D88EC559101C42A3B4379142EA7520513A51ACC3EB818C00450CB7F485E4FF855A9224FA37A89177C09DE1246FE245C366F4A96513C2E8A1CD77C4DE2F6422FE7204C4E501EF7273A1192AD5B2E6D974C472562BE666BF1261CB15CE9F9E24709AEB55A96FD838FD041F1274554046222BAE29F29666D3FEB06145BD3481A1B82B65D3FEF880491F39C21EBB2C03C70648ABFA376749E9FB3BC3997CF4A7F94932F37D35BEE18597C85A223A45D1A3D3A426D0D7060CC412483495EC11A0535DF8D8A6B92DAE50FB18E37848364E6D97E2D4B7CC63BBF3CFA62AB2E32321016A48B8193E82A2D3984ED573CADFFB90CB8357D85F6220E99EB60063A84B3E3841A34FF102075F90672A3AB1055D016DB70557AD7FD0D02DAC7D6794625010E685FA19F2334AF6F660473D066E280359DD5D2AC4080CE28D228066BA663A8040FDF8E72AD16ACB7661C993813E5AA016131BFAAA80437E04167E6759F93C453D64A0EF4DDA10057FB60D522F059A994E679027037BEC7F60DB81281007CFA16176569A663413265B7C308E04192E9D87E48D8C15259BC288672969A94AA2954E2D210AAE043E02C030A11E96C9D0983B2ECF3BEDECE727F834615731C26D5F187AE0423F5B34408288876254129112778DC998B713490346C955AC0E0CC86D52423A8C552E815902E686E28FA466190B2FE042A23835A45B3045CF300AB66DF4A0615C93164EBA72FF460EBC6A728A7B304E6719C719FB18410D3DEDAA751C02FF06FE18E8C26485580ABE81784D3C73CE72F734ED688EDAE3A06D8529FCF7900C029FB0AE926A601FF3739FE6EC7C5891D32F91DBF47985004A766B3C93611DCCC47D3E6F435EE0D21BBC0281D4D273172B3952617E24AC292037D6047982A6BDD0BCED4653CF5E07F7662A250107AD9EDFF0396913048030F87ECAF23310B9A83E01A46EC9CDD028CAEBB3DA078553C3FB3C61F7D41ADB0AA0B890447A0C07BF9FEB04DBC5C7162A036007D5C8E3199B21AC6498DDCB5760292783B0C8FF3D88431ADF9ADB23209197B5A1460F7BE09826EF91D1B6A6F011BF52CAAEF9AD34488C0594A3A4EB34CF41527EF56DCA2EAB729518984A1E30FA5296759F08EC6DEBDBF5D1ADC02C6DAF13B2D69020ABE3419DFB38FA3A9A8F7782A30F980B09BB1242DF82413B2FFF6640126088E213C9F8C3C2A6EC4DB005907AB48D1DF60382C61A14683A5030D2F81A89CBA572EECCCF26976EF2D61BED698F127392E71C5CA93C0C61217E92ED22FB1870E425100E46EB34B06819DFA694AECBD922A0187853C8BF33BC07EC68CCE254D4F6B5918A0297F6DF27C62313B4C67878C91A0410AA1B6142C0BB263AF02FF80BF4154BF14CC29C5ADF9BEC2A205031148B634B8D65E688D3C4431529C03B91784C7EAD5ECF020842A47F327142AACBB901586BC571BA17F2BDEE218C53A9E58333033182A667273AEC10D1CF900858DCC504694459D0840528C2F52FF2D0C080DE481B0B169696E3B36866E50CDDEB398B63989382DC2734E032179F2084FEDDE0CDAF2837E8C11444C1470DF1E054CDA96DE7971AFFB6A1E8B34D84F4408BDC7E932F5A2C63214891E0C16CBF8C20C145D18371B1DC6A339FD19116EB884AEDA668D8EE2CB85F68CD0B9488E24C06C75F53A6D9BFDBC71C8BB17E1202EEB10B7D3AA7F9AAF31457DD4981426D21E91DD76CEB24EA982E68A12F77850297B0592C9287996834958C2560BAEA0F3BB8E76BBB1224FDED7AF1D4A6ED880BD95F128F32BD392B3FD343D58E8C5BCC9B1424BBDBEAE26A2120AD759136DA7F28381B9F84CC902C4BA639294255F6344483CE34B37FDEEC50544A1D26184E76D05A6B0FCE9606870FD32E0E2619C7D6E280DED92194A60D5E7CCD7487E0602BABB62FE3A4EF15846B7683E7F0D262AC233C735ED78DE58194D288435700A78661B753ED26054DF4FEEE0923EEE786E5F6FA1A6666697D33436A4E9A3054CC82ACF0BD2FCF903464A2389C87A67D669538696E658D1CE6DDCA9AEE1131AFCA2DB21376D43E382A4B9810C454E1B613ECB972B9BD60FF35AE698B653864DFBE219E9205FFABA6700B514F41936440D926B3D1DD2AD696E8E86107AF6663A43FB4F199E5F271ED3F6759C54FAF4843B9EA3F0814C7301C77995A43DE6793E15027DF52FD7EAF2AF3811667430E52451F56DDF85BF290F8811D586A3184836217DFFBD78CE485E72D209AE267BB4952DF8D301A55A6BB1368FC522A398D35B59FECCA5F89C0635D83E6730990983D47AC6AB7E7DA85F57599C3C69F85DFF280BCCB38C2F515AA60957ECBB2F2010958C234ABE4CC7D755A82B017BDC2BAC5FCAFA7C9D392CD32769641DBC5D8C8CC3E376C66991A0CCB4B440015B3084665163C4876F488224AB6E8E0B2C215C541084A4A15CD774FD71F110121570E8C05A06064F8FDD1442BC841A78D6FD42B3D398EF2470231BBC717619E01144E51952F8F1EC605DF00F6D98D5F18C1D26E0271BD9D51CB27C09F99CB749286A46DA3DA77F3645D181DD9E24D3DCD01FC0BE2347E588A4042A49526BF89ECDB3B6A1DB48760C03B6362F42852FD49AA903840F557EA0FF6920C678F1AD06BD5C8E7BBC1343673FBA164218E09720F453E54273DD42BE2B20DE1870C7BB6312F833F76F2C5F2250414A5F3554AD28548F29C4273AE67830B361ED8ACAD7A4E13C7C832DA0958A9178BAFB4A40017CEB4E9697F8B0C055904D1DAFB539D48DE75A3D2BDA6B7E00510968CD9C5F4B290E25C79E46BB9562C5999B4762B053660FCE627FC6E92CB6288C529A72938EF591413371E263D8D71949F64C3001BC323F8F03F2834E34FE4CDD96E61305ED478CF278D3598E07960B6B749C34C22F5875FC0F5BB93EBB3A157EE54200C2360F08A03196FEFCD8471360384F32314E2F90CBBF887BD63C444A097A04F9679E373E9A6A0FE0FC730E471483485891261A1579A54B76BC8165E243E4CB7377DF346237CB311D6E1758F4BFA3FE6A0FF05D3B82BA44BDCD5E8C9E418DF41F81DC989967D329C3D53454D6A4D58D41B8AFA036DE038ED2D8E9132E9357740B33B918C8CF243DB3460092FB6E5E264A1924AC4F249B9A774493C4C3D2AC7B672B1E998540A2609D4049D8F5F63CDB1ACA366B7278CF90579926B3C55230C64246A65FDCBDEE57C8BDB34E36508999A9856B1CF748DFC999827D58558B77B322E0091E1A630C1C486997FF7B56E9CAA0621E569D11E0C3325CE6076F1A30F812A0692BACB338BA1F55B82FF39D7498BEAABA06AFEEC76FEDD62F97BA9027666DE9F49FAB64B54107B51988801325D9B51119E4AC32EFFBE13D1FE28ABFABA759A04781FE98E6881F29C994AA1BC5E9D0DD9EFEB18484D717176903A74D3EE1B27D54CB0E33CCD93887828523E41D115F7B51E7E0D5D43DC484F1BAE8111100C3F30E72968DFF0462780AAE01168088FB3EFCC30F329667489018AB35A9691FB76A6AACE8B02B872437127962AE12BFD8C82DBEF2DD89EFBA3DB2B63C2801C0EE6BD331D21105C1431FB867FCDC51C02DB248861348C543012A8878A6056C8D047270461D51EA4D0A85FEDF1AAA416029B523FABD44E3E25D9F555A1DDBD2C030F3527F758A0EAC9F69428D3F74BE6D8427AF8B0C407E0E7818764AB42004829C8F102AC9822F7766AE94A40A8F8F74F0C0FC1D601702319530BA0AA86326A6C555845D1C85DC723A07FDFD4C6F85C2F5C4D912CDEA186D12EE418839CE1F9456A42C603605D42BDD9C41DC40DD0720BF99AA3FB0E2A807EA3A406894C81570A323DD8DC67F445A53FE0DCB0C0835293EDBD4F850116D2B3896D1ED631455D9C9C5255BFB20453A2DF76E9EE3460A09C24D08CAAC998A080677D755283BCF4E4EBED728A81A6713B1A988164586DA4A96E666F5ED8AB824D7931C4E5EC3B6AF1D623A3ADDA1EAB6E7ACE419CD63E81CD93E81EA4D2A64D25A59DA51999707157A512B4033F52C89ABA4DEAC79217F725F8D881E3ECA42FD0A7C08520E273C9E2EE15D3E704AE06312116D3668E85E41E94DAD2FB0F4A506FD40431251AB643E4A866B361CC42963DFA57E9249904EFCFB430F30346A02EC19997F96A3FE2AEB5574C9D065B68804109F32ED27CC6DDFA24D86583C58494960A78AA820D143542694708F53973150FFA3C3EB73B04592CBE8EC365EFF2EBB491298E16E11125D37B6072D67C21E87EBC8B410943AEB7D8E23C17E2C45C05F46AA6F852759A78B07CD5463CB30395A5B407765D9CD99B00D40176D050D0AB736D663BFDA7110520639D1536022A0F99C9A4995DE1D6AAFC7F8C33423ADE5376E95744DBC797742F7F24258FB3AC93249B5BB7112A23E2D1F8316803D2FA940C5B5D3D66D13819370DE2FDC4A90494EB5CCC69A1421D0DF68424A0CCB0E53A7BD18682556A540436A8B43B0213F27958CCB5D5A474CBA693567663521140F3A4BBF5848CB4D77932A1050299404ADFA629566CDB21F91358CD918AA65109C971335E041A0661BEB972AC835A451A66B6AF503148DB5587540D4137A770B16412C9709AA6500364A38A24CB9C826EA367A62D330403E90A0BE83BB4E3980487F0B67A046B2876032524B5F185A6E4A9410CA7D9231DB109AB0EE09B25024F570C2B57192AE98AB0466A20D634F266413FE8F3E702DA112B6DA139191CD5D398ED9D1445A83E237954E9921C55308DCB34EE24B7068B69DC4A7E096A18FD968C2C3E23FE75249341C79366F4806436EA5EA1E7CBAEE87EE61E50A08EA13666C241A2F35B04FA20EA1D52C809F0C30D9D80A8BE6903C93CC448608FA51ED25A02A9D3DF93024BF9D262EB032A16DAA0F16D60BBD333E7E211059947CA4B794D581C56B8DB4E46921D2928C6A9AB5F58BDD0C8D9660CB9304A5FA488F46B54D65161AB28B2354B341A694D0D0BE2C6847BD19A532D5E34129D917CC1534B1EF2A3B69317DA0A5740262DA7626252A11B4F49D6C95E215762B642384373C4931EB8B879EF1FF2355702DDB6A367C9D82DF33539F9C15B38C764A1AD42C627A691423702064EF10C105B54BC05EB2CEF02BBC15F05A6E0DD6208E61AAD7D95422BA752DF35B71EAF8CCF4710B7908C7BDD42E52DE294BA2844CD5F801242BA10BE8A9839C5F4E51F9994D0C147C392FFEED44D66FE8AA8E5DF1FFF1FD88D7A2ECD4780D6586B128F6406E5299C7B5C903654B0D098D6D0C605009FF29249D6FA05015A3D8FCA3476F09097832ABBF8C5709DC14F167F4B256DB7B29199F481060BBDD60C3BA82C1051A5D4D8B6D6C81CCF9149720FA74C54DD17C3729E63D1AF55CF4F29A854D1339746C20B6BFA5C9A6408DDEA5C76EF52051BB31B2EC2145C9CE79F7E8377BA78043CB3E2B64463950FE6CF5865844520276378A55C34D2E861072D6A405FB28AAB1C32E6702FC93A3AB9A45FB51A0287E5286B94525DA50F0E95CDE3D82F6D8B65B13ABDF66B018D760F7C51C78EC19EF0BE026D237170AE0FB000EDC54790357C4015DC70BF1478627A3D021483D6ED3907FEC1094F10E03E30C3EC0A1AFF76120EE48D042E12BC6F27BDB7910CAD39D562B74B056E6E87132B46105AC0739CF50176FC6232AE0E673AF496C6201C95CC46254164498735CC12198C50C925E9259F041445ED012D31E10631782C8D69143A0FA5B0DFF7324D33D0C419FAD821F41392823D7E00BC923554002D0F88179BEE302EBFF417120DA81CA9797D29A92AD4DA741B0E12E81E09CC209C90095BC99EB7C172E1221A208E771B293CFD706660260635DA6AAFAFCEAF97CD119D6279C0EDB5841E7C9135C563D9A6152D6E548A408BC8F82AC2D9F1827653864872440C86B35DD2F3621F23E59D5B005641735AEB8F51296D5D5083DDD16846AF1F871752A3CD23B8F132BC97769B7684AB075F4E2F216249F68B0C9961A63A836684A439AC01EF6A93D49B09184EB0695D38908F25A734C782982803EB09042B15BC7E7CE2FB717A10771C76207E066B945A03A97A9857C44F1312C5C6BB57C8BE69609B1EA9C241F4A2E6063A92746B5E6FAC9AEA6941E78468A9D464473206CAEECAE33BFCB807CC8C548CD557AEE0E63CBF87971005E435A0E0EF7CB7ED136E8850DFDE05C5A1E5909EBE4DBA52E38038785458FB6E2E6582988D56EF366FA168586F2C3A24B4EE89631D7EF231A46CB78F50FBA33F05AE10F484CBA51DACF02FDA944191C074097A527249D485498B3356CFA2436ACAA4E0E31DEFDA70E2A4936040BFA3443A8A09771DE9180AC6AA017E952B8D2D0023C3AA2C70C89C78B7691C36853013FF77155D6427BFBB4FDF338B4F245607B5B2A1D0A6A1E1774C640D760948DE243AE11CFD829BDA12D117898686162EBAD1B41D6E7B2743FD6D3134313445E409B4732C1FD7FF5E03E797E7C758ED22A1A660A56BB8C53D11FB1F47944C281BA6805642E1FD4D46057C1A86E4D300F64FE88FF0939F154DAD8F31224A790F72A83C83E2F23FC93D3855C24A3027C531DC4C92797A33554468CAE2B88073ACE64A8B506260606540F5024BFE03627AF72FBA4ABF713CC7DD6F90FDAB5490544F936BF2F44E1D501DD010415783C04E735A620FF4195B5D911CCB470161E2B88D42A3D84392A814C668FE08754A97009366DCA99038B81E30E5FC53F4A09F2A10E4B8C40E687687FD93B931C2329EB26742EC6907ABA5600C9415D7B9341896DF4508B9C6FAC0D41E92B72604B0307AFA7EB9BB7B32DED98C62713971BAE10BD258B435AA24CFD91956BC7F96128981EDB49ED70E6A20FD233282C23DBD99AB4787E12FAC6EB42A289A5DF4737A9DAC91079D85F4E14F28387C7C2D0237625E5AE3642A8071AA15740A2B11E49C152687A4E346B243529DE6EB9C9780E9DA5093CEC7A0D74BF5581C1B934827023AC4A618BCFF934B65CEFF21DFAEB2151C7E0C4D64F91AC082801078E97BF131838A3D585B661E691F31AE0B7AB306C91AA6269DA4940352BD7E70CB55AE2483B37FFA8D4F6282935834BD2A171D798A924A78424E56F9881F0C98C2D154B32412B0927A00DEC8F42FC3AB694D6B3503EBD5908385BFF6AAF8FB92DC01D9C67A204A4403F89255A9B06AEA4F8F781383DF3E6BA5ABA36DB31DFAC3F8AAD0F1089DBC8148BB1886D27F4AD398A8C3C4D3BB6077C63ECEC218AA3F8D9CD6E0264B6465BF67869A9CC723500EB937028FC93EE67071DCD10FD50AC8F8D706239BAB3987499A2F0AA916C5C98DF02EF68149F28F2C01111A0AF50996D4026AE1A93CE35C32B23FADBF9010609261BD25C8661BBE64F1CB2F1926027E80F4A3466F04F94B34CB6C3E08771210E306CA8090A32B9861B4B4BB9859548C0A9C3D3D39C4BCB5E55854006037C026904829F30752EC29644F089A0CBEAC2A2E8873A081EA9E5B00FD97E77AF722A15809918033B48080F2C8B9FEC4EDE29C636E013FB4B17738CECAD690E2E8160FE8B299E912CA5E109FB53BD663CED0406D28F1D39AD70201906FA36DB09CD5EA20A9D6411811B169A0C81FDE92EA8F436507DEC21086320CF297070932DBE117427FE13BB4BECC713310609CF0D0077743D618AD60761580D40E8169698DE22934613B13CF86243589709AE6D717F78197AD045EEA1978AA11B0DDE84D347670E4F8CEBD5CF44719D1C2E7D2271446852DEBD3BC14B96F9C82F81B1D460C2637C9D435A544C556785A000A00F8A7033174746581AA4925C923BCE4C0CED41402AA1D107A5D7AAA667B19582DDD31864146D75DD3844042C1E994D6A2E2690A53DA29659B09854674863837B013803961074A6CEABBE1A4954B606FFE63C10F5B716409B876B8F4525A338802B34CA2668FDF2E978EBF82984205E2019BBFD865249A560817DB872B03D000174B7EE0452BA00E20840A1338DAF31679233BFED0C4CC538462062079BC8005DE87199B0DD680E9ECC5037287EB18C08BF1691987637AF6D08736D6E0D8905E2E1ED8CED21CC15FF3F61465CE2C808BB7DAF1F21B08C614762E4D3F42191767D41CB62E32F049DDC6D2FC51F680E4D89CDD6C48A9D2492D1696A05C1470EB680C8F90884B604E25C169F53C8A39FBC6D5BA02C5A474712BA56741236BADC59497F3A7A251CC52A82AFCCBE8B5E729115AF56324EC1F12A59F143ED014BA5F252E2A8CE75B449462002A49977F7C888FD4291E24EA1D0ABDFD8BE24494045EDF43264DADBDE2310A51923CF474C1FD9AF84E877F7C745A2887501EC70C394490ADF817B9134F84DF5B1C0FA17942E374D854B73841E464C2AB9F258252EDA66921804375862E6578EAAEB419417BA21488397E9BB68C99F4127491F5F894089F16A4303D3BB862C17BF225DEBB6258810B317632DEB21F9A9ECB11C5FBEA95AAEAB286377E5B12A71C11CC44B2D656873E6903C729402D2912F37387028655C975D7139248E06726DD12667C8074391789B11E0C68EC2011A7BBAB648FB754C20CA7F3C13D710C4611587D9A1B9D404802F8186963400D6A163D306697C072E638AF78B0E852E917170869E30C81F09C5DBB216367C009F6824D596AEA12F54F1074128ABC68E2F02A5ADBA25377BF1F2D3363619D55999C6C00DC8EC2A3053E28E3FA31BD2C9FD52E5A8A34BEE7F6BFB3A6AADECCE36DA5B77B77D7F5B12449CFB45C3971ED679ACD1427C5BFACAE6A03C3C8EB83CB6A73BB6DE3C5028110EEB4754CCEF4644C1E43809F22EA8EA5B2FD49E3783762494FA7D6FF65496115B993581A0283E686E66A5EC6F6B30CE8CC4258CE23CDA81B19839A5DD7A957515960F2875802388F58B7E5439CD06E56445A708E4F31899DA1DDA68FF0EE90100C9516364A1CE8045D7862B094BA4859DE7378B03A30495405BA7D2316EBCDB5C5292AFECA096A273EE3BE5EDAC093B3675E6AB19EC3C0BC7B1D88DE28CEE9377F9AAAF31769AD4432131A73218B3A11FCE061C9F297581A84E1B277A9FC4EC25D9627574F749D3F843ED73A46C8F0390F6599D335B3A7F591E0F9AC9DAAFBD43E9D056E568C3A2DD3C169052A33D94692CA33E784219C7240A537559AF9FBCC0089453129069F62F27D1F70CB3EB0BE9C555731C47EE166EF9500891513BE587683DA88A9AE9C34C1DF208417625B792997E1AED962BD2719541378C78205DE1F0B38813D2208121F46D36C000047E35098410132DCD171FD2168ADA258EA16641272A19A044D9142C7A861161818F8AC23CD3B07DBFA21AF9663C465340272A7E6932F1EF702DED1ADA76E01BC8054A906B8A11E7128950C05E777773E172831B4143B1C55D34679A6F2167DB63012B480E94956E46BFA5A664AD836ADEDEA8E02FCC1D01129854B3107025726862511339BF37D8BFA97D014B43A5F5AE93621CC7E21B362478725C092A534320B4A0C7902F686578CFAC97845964728AAE31EFD4875B2E0EE4738950693A4221AB31FCFFDCDB6EBE4EE727EF0D60E968DA6CB8B503E7CC293D59FA38170329B67F3D4C059BD0C09321F40719DFC839C00584E86C3B7C52FC88332B3AAE13132A6556566F56FFA0CF65F4311B98A29114A89EE48166DA8A7DEA2AAB7586A0338B3E5ECAE3F8A284B4E1F5B4F13BA7B1C5043FB4AA22F226F3242C7AF2AEF552885081FFE62278268C1FF5A3F24A1BE8F5863A867F136F2646FFB231A8C0CC65CBBA7FD0437BA1A80BD29A1FF18F08FC74BA1875BABE3361BAE394AE78851E4161CC354D16579C1F3DA19B5E8C4DBFBC6F625D75527A595969CE1F59764080B0D7882C99884F6F3A8BCB3AB63616804CB6F7F728D0110D827D8995DDF29731D4AC6F2E91BBE034A85F6434E4A6E93AA30FFC349C3ED2F7021A4421842348C90D606096D10E4392F8210BD5A78EC80C9CAC88005F381152322B40F3031AD300A1F7B0A118F83BF17F05E568F468FE0CE9ABDA646125CA920A10FF9020B384A2CF8767601C2A6CEC71553CA54ADA7954784D74B43E6237C8A3BE4CDAD7342F6603E159F4882F13261D43D41F8ED6B392603FEF00498ACAD5DEC2DA528D6460D2281628F8A76946895A18D318184A40A81CD50A90060DA062C8BBF15E4907821E986191DDB63AAC26E8CCD870C0CF1B7BE5C33ABAE6BAEF55A9FD89E59E3417DA25020A08AF5604179A2232F45A712691C3740F2817A71C5B69E3642BF57E35CDBA612BA5B5A0BF05F2D7C65F51785F90ED2C17898D4D2F62E76999C653768A03763E6D85C643FBACB833DBB1716C9F9E9E86E7A9986B375A2D40727146627A3B5B93CDA240E592942556A42834F76388485C1ED23B0752ED21836EEB731909783A19AE2CB4E487A157CA2C1AF6ED99B75885065FE273005435E85EC66962922C4C8C246160A6AE31CE13D04E48A27DA835948B3288E762C687FE16B160F27C54E1DDAF2E2901E28C1690A90118B7A04B4FA87ECBA45494B3E77E183646F8087F3821266E66127C5A0729BCA1D874C8521526966F3CE1094437287A88234D956B85000C54F654756304C23C4E1E979F985E134C4E4057D89710D3CA2DBE0CCC8B96DD989B0B5DE5ED2E85D9BD9AC48CA0826D1B00024C4D788361DB80F995DDBC23DDF0350F04A67F35AD819397B01210C6AB63D98AC308CF8EF0790E5DEE9883D11D9E0AB8C097ADFA2B2ED761C52B86FBE81DCC1C89A73B76D98E3C694099540C2A2941AD89B713F5EF4CCA5477ACBB646F8AB74B2384EA7DCD7A5D20D5B29DBC49B2646732AE6EF051C879C8AB11E16EF80E2241E7852DA941B8042D08EEE22DF4ABB708B2DEEECE28A9AEA56BB7A1654DE8A77D2AE487099EDE575A5316558E3D099A0E6649D3BF37DB4077D807E7D1C82D9D3BBDF162CD486EFCEB1975696B9B21FDA1A60FFF76304A07841C05ECBADDFFA6443902AD94163464E51DC489A1295FA0F29E5323C6CDC7F85930EA3B12B89CA5A808150430B1E6933975FF37993E65658B212167C8F0A359A33470986C8985B82F15FA9E0839837146605D392FD8ADCBEA9BBB8500B792BDC5B0633E98DDCB5A7B50C798B09C2EA5045694E6E1A1ECE77681C278C68A014EE030CDA031377D2F7EEE3B4558151A03CF2C9AFF390589F0CB3F772454395F63E1FC6B151BB3AE6218FB7F3B6A5AAED22A0944CD916023EAC65FB7A47C919B53AFCC0D1EA295B09777A4191D647B95153E70596D8D3671EEAF863FC6A0D956D0EB38DC91A72C6F68960731A17BC99EA47FA484D041A2AD6A861E0CD9C0EE4CF618989A3B146158088907584F0C2B1350E9163465CA8168B45E5288E8FEFCAD919D1CC54AEF5C82973A7EC74C23E3A64D0D2C94D845C360E43A9C38D8E26BCC3BE901DC89B98CA50C71453110E0D587D1BFF8B72D3066C8079C08FC8C179E09CB9F02FC3677A07689F2212B0793FB9303F9414152E80C24EFF0C5D7AEC991BC42BA622D309D2EA4716E31A50739DCC97A6269D3E66B7D3959AB40B8ED047B4FB20D58C7E067D28520BB2F2DA582D9EEAB21BBC66B6B3BBB6EA1F6C5BF1E4B3707C3D00A53F636A40AE882F101916397A32B8502DF50494A304688A189A4BE736F56227A73E4A5BCC68FBD548CCB46EF5E52C65CC5B3CC52295C77777B323BE13C5F0FF5B9EB1DDEA64B4A5DD7E84BB1870AB5DB087056AA2C8570BD444656545C4E41BE2D0AD22D1658372AC4E100524392CABBB49305B21FC643C1090A065224A74118C9D2F90E8CA3B40A21E631FBDC87B8C0A585DCB7D841F4996B15146C709C728BA7682E1CCE7F6D29E387DC918F51E3DC2FFD19186538B7D7D14F5BB021322A88B9BD145C2152424076C513561C1B3EAD8CE7BFD850D1957C5A2546542F332E320A525BED6BC94DC6FAC1511B3810200DCDAD2A22800819350E93FE7A967E0DE29BDFBDFF40E851774B73D93B0F62D07FFA18827E0565A043EA9CA41EF5C5D8578303AC0F3EF83A19F592A216F1E8ECDE9B8E2FD466DE77A1D2951B4FEB21C121EB8F032A2196383499DA9838C802322B72D8D64A837B831FFA32305CECDE8860BEE3AB03EE2159C2D8C083A889D7670E14643FC9620101281382359546F190E0EE29D546D546A0B45F42E4590265AAE23B9A877AC0A38321AE6FC787BC7868804E431D4290805475699860B827981F75386E7CA6A90A1A18C6D9533A72320649368E6577B1D9BAD80DA0F673E0669A4D3545AFB7F0810A03B218BB02F6E69B63DE570FE001F3FEF952FBBF30F85A0F0A72479ADB8BD4ED5D840335DA8D8656D1D39CA0C216DDBD0D276BA8D80DE10975C9EF520E8E1BF01796111FCDC33533AE0A338CAEAC146E218AE2B49F9CB021BFCA75B619D74A5BBAF305C213F4D7D2306B24D00E18A57BF95C8079AF0C31A6B979AEE3690E43CA70AB4E8798020FE173A49E3C855FBA4048E860E6647F5B9F10A05457459B4C07B58817E6C2CF4A230564F2020CF6D2B0DBAED99805E5BFEA9434DD7B45C892472D12E2E43D6BD13DD3A81C045BD6D8927295CD5D32F06180ECB43CC76CA0A8CEB5B0296B5E80E35FCFA4599B2DA64A3A2DD63ED64F680076111908444B94E124457654C1EA646638B8018344B4B82D1A6F59022C0E8817FF891195663F8A2AD043F6F59199CB17C5CAA9EBCA040BE65E617B7FD40BD0E97B0B66CA53E8F7D4943822313623F3748F61D4EC04396B9C01D2033CB097FFA4C076D81E82264336B1AA73F55433114D52B534A11203F97CAA7028383EA61807B4E88F85B491562423094C517044786A09EA4B808CF67748E34A13BDCF805BCB3A2A0F4326A8ED902ACA98A3456B3CEAC037DB88A0488E9973016B28060CE70378A86C51523071E3F4B91B08E8D97C0C9DCCAF6D3A143ADC3220DFD5C75301032D9A10D01E4A2AAF88E8D9B39ED25A5B2E9E336271E7C3949DA8F7D70C7501ED7DE7A400C2902D242BE5A78DE6140D7B4D1B625462D5254E9D83130CB7B5CEE943926B3F33B84F0FBD65A8D03C309B4F6F136DE8DB9E6653909EA5D65D6A5065514AD5B2F84509D456925B2C9F938D9D217F2962855D09AE1C836E54AA3C125FDFA020CA7DCD50CE2BD712EAD879A24533F8AFBB1E0AF0C54D60935E7F87BD9DD2CA3F49554CED5BE81897AA398301F7C0FF2894680A0042B52C88D8324E4DE5816C81407920E2A2E70196C0C00028C02B4315C00FD38BDAE9A72852923A77BC03FE28C07CA3CCF8087A22924B0FC18C9BBF774F55B8A5E30FF5478DAC9B28D390C7F82EB327F8EAFEF4AC0E9E25C9316F72E0D2D985C84381F3EAA924500A86CA4995E68A361A2D7CCE7DD05891007FD44A9E43776963DE127679D07C1E6D3BA9D4F2AD943244C18B4E869D273240366958A6E588A6CBA3E6F8D4F8E1C021D0BDAAE3B40E92A7B63AC343094957F2F4517AAAC843EA56159AB85948FA98DCD6BE81664C2DD2C882DBB91C9BCD29C1157C2C47883197D64179F12E3CB4BD0B778EC63DE092DA4FEC408045780D7F15C2712ACE9701802D3B50449550915701FF00F32CEA32565299F5137E6BFFDCF2B1540467712CDAA4C87CEBB42D7088AF4EA726B583449223E01BB125AB9C821B4F824F24791BBD01BF252BBCEC50060A69EE1F977BA62CCE1BF92C2181963DA86F7790A34C367A293F459C2EC4D488C9E574D1704D8FB112196F160C046E4C4AC0F6EF271AA947D88F7DC0EC7D7819780F3243A74BFAA6B66C2A9322D959219F465D8294EAC9A12285B4E2896A87F2E0BC51295C553E323F6D4E133FEA0E5E545FFDB971D1FF36CA1B45B6E44E1213DE0461C5CB01272039605361F620D431CEB205AA6A1F07F9FF9A4B2F0963522B476600A619C4F40319CEE9DEEF8E6635BF4F84056E97102329ECC6DFB80DA921D3B57122B8E5D9CA835BF4CE7431DD5F659AE22D3130F1C6290AEBE862EA4AB896C5BCA7935345D605A12E9EB45C21CEC26C2BBFA5E961F6CA943724F384319D252DBC66EB9210E8CA0032F5A54EAAF856AB7FA10F417310D09EFE3B44D9E55918141388427E5B4883B1F06035EE69E2A04D48D258C730FF178CC73B40415C9324723B59DBF07FE376A1255405CACD341EE9754024403E9215A73C33D9199DC35D3F9788C40E8CB6443F2040D216A522C1EE1612127D04454154255B501FCE244C5FFCD92FC7FF846E34CEDA2BC1F4A2132158C06A0999DB4B97A8E7A91751B750391F5AE003859A13AEA355CED13C8DA4E7AE3755AE942DB818F8AA1FD0B485131A53E823B81C316FD0D1185A452EE57FA09514B6464E64BCB92642F0FA43B71FA30C6918111053F957A91B41A478C7663D7A389D7E206843A83CE3E05218564ACA2BB6E1A283F3CE0533483A4D14AC54740528394F6158B12B0EE1D28CB26AE56231BA4EF9F3890AC41CE4D86792660E67AF491B46A6324954149AF79E12E452F5353F75E0A8DADF3C0779C3E63435E1438C320986236626000CCFC182896DC07CB36478B2A8453536277EEABA63E2E0515816D03C6DB95A05A32B984D50B0633535B2BD64AEF002B7F60C9370F844074715FBA08D34FD800F570F80FD269895ECF007BC37988AF09D14DF303C367160B6D09AC1E5640FBD1990272D363BD8EAAC07C03AEC98C49A80697B2D09338692734DE4B928107545AFC9CB62E081EF19ACD2688D6F29A3A8E6179C835A5118E129604BF94A75D08F96EF9591FF82DB016F1B8562F4DF888B170D175E50683AA19F4E94D8B730EBC75F50F3345DE17F98F73D17CED558BDEA0C4C689C3B4E3A46F3001A28E62B2CF337B5C28332EA5C046213643EE8F45D416447E84CE62AE4F6F853F068FBC70B821578B8BA41C3C5E0BD6B110A3B6CB0EA698D0010A45B8A7594F8077BE609138EDA8903AB3E7BD5AD6DE2D237AC92C7300076C3180A89BF8526EB6047B91114456FC813C20169E4E1BDEEB08A3FD15C51FD010890EA56856C42494C6F725031E0189F06ED80967707C22C9762C98A43BC07BA4CD13F911D6F342D3D663D5F60F9FAE435942E0DD7B64D197A082F080500291D79EF923A9CFACDDE43F059312A0DE76E7978ED69C70B4962ED8555DDC11452D33B163A508DC1DFC7940BCE7974B549A906F8B6AAF455131200E29EDC113CEA11D9F82CE643DD4511BCA5FA39DEE315E483601CDEBFC9196D92208FBAB0155FCD2A09C903F0018AA9B3913E6CFDCC307DC2B6A25FC7F21154821BADFD3A0252ADA968824AE2B88498A0948AE26D421AA6347C28901B5A437C1988FD8F6558A2A2176DCC41F6D42C2BCDA8374A0F3428B4E2B04E630E2E53E791028479C1132C37AA04E35A83F74C0BE0B71DC67B7E846E4B654972863C9D011737670C54A94E6B87E0196961B831D8BC89BE14CED0D69172CD5897AB87B2C1443B8FC46A7D8D91686BBBB1C1768B4D2439E9455271A4205C6F5EE26C45C627CD835B4CE0ADA396DA301641C7880E28A3145C841939B59B08F02B8C22C9D8EB02F4AED5E1A575DAADC17040B93B9069C10E985AC22DDF080A44458904D87A1AA106DE4B93F1648D36ABC89C80353C5C261A96C976AB63607FA22095C088C62C46550D7D15571076BB1F55A45D70D55A146EC430A31763255D290A0A5E8E7B5E366FBC616D528D022A39F6611723D43AC3E212B880F970E2196E692F8DDC9A09C122C5A57E6ED8A5EF4FB1F3ABE1FEE93D8FFF5140E0E096F38F3B94A53ED8071F544C3EE5B91CBB4EE9DBE7757C6736D83B6857D8ED8FC4220912E0495384C99542FB2D4A397EEAAE1D7161DEFFB35ACA0FA6FACFBC3F0A49018AE3A0A3D2907783D3186F0FF415E853E6C2887E01B033183210B8B0D910069CDA654AA6473F83C8CB217C6858369F73FAF6FBAC0052F5905839EAB7F55A9AF05E4CFD311DD2A212AC971791E6F89E621CF9FB193341207A20901597CD39EF0EE43D182B9AA11BFD18FA894D08522C038762027C49F37930A6D4B442D8450101A265FDDDAFA9138352E15C376068270769413110DE83A559EA8FCD0749B760818174207E713A80F54860CC4248A33721F57E531B579829D97408EB3666E47BC3407608EB076586801C686576440DF0FD0814B1704FAED76F01E8A3DA8664A1B3B070A3BB214C006AD0ED54B7C1724B28CA86008D961A094037EEEC0CF3330995AFF8FD9E3CACD820256F0D5E21B788ABCAA5282D74B3442581D18F4FB843F238AAD72B25A5E64376C2394ACE2308710B1799B0586040A135F5248DC0C9F0A6914C51C9FE1C9A4AC6C4C4D2A71E7535BB9643D87B5184B2774E1224269CDB787C403A9CB9C71B764965AD754D58601F29E1D403181EDC29A57F172EAD40723DF6DBEE0975337A0ED3CA20740359A892758062E97AAC98A6B9422BE031F119D910083FE7D780F50D55DE53B502C7BB623A8EDAD819AFA8FE82BFFA63289697795479C88396AC59157D6A5604350ADAF6975C45BB564486C46EA20E61F94CF3E0168D3CBCF3588B406AA1AADE22CF3129E91C998E62EAD3211A851ACCD13FD8232AD344C4147CDD1C338308130ECFEFDA8746ED0CD790596F142887F3A1B4F09C34B4F0327DCE6E46BEE1265D6C749622A1A0619D363FEF1390B5BADEC4D204B278820386BC20F8459730512D658EB11F150C58F2E01F21F262E3609577C80C580D0A896F309DB48EB998EA9CDD84535483DE96A46CA9738D91E00899436C24A32B6373616530EC33BDC39404ECA1682DE771BD6CAEB399CA3AB0851C4A62E6325DF72AC6EF548FA6E30584F52FA82123A72558C0E2FC632934924421E808FC306556BAF84E1B0218620FA3CAC343982615A5CB180B61D659EF1D26F6DE302EB71A3B4F562C22024018F2BEBA680C07957FDEF2EA4CBD813D786D1665A41F4723D2BAD5220178355361F0C2459390F2A8DF121AEAB228FEACE5702BE8A24A2824F5F710DD6E5E52301B152EA673778F0BE3EFA55314F43C577002ACF31E0FA904EA130712268270D867EC9A40E5671510FF23D0F81F70FED27B8EFFE44EFCCAC0B37BE7BDC146FD4A46D8B6F50ACF08CC21FBF17C44FBC0E2382D3551FB7B91105315BFC032004F968F31400B7D418778F52903149E143A0452DD731614DBBA337444417357DB8E6372BB5447715841952D7F72BE2E8C7FF56B5B1C017A4E83084519676B1F8345670B5E7773CDF61905CF1173CE0DC3703045214E6BBC32FB7397C6E0C6669001ADDAC54B7E29D06486FBB650F06ECA51385BFF5EA4CF84E3CA90D1F2646182F6977A93D4BB41BDCB4AAC7EAEB82EB80343910EBAD848C6DFBB70CB021296F47EA773F31C03ECB6920DF8C904D00640F8AF6A20E3194D5F42CAD2EB5DA0436478F09398FEB609F07C930CB756005A30EBBE192025614C409D84CBEC1821D7477814C687F4A8A7670C54161A222EFB9C09385720AD26F23EA7A1BE11842AD6CB7AB0C931475AE33618CB7F695AAF3E9DBF5C0689D13BF78DC4F7D0DD50AF00BB2328A160B26C5C8171C9AE61271321A6A54A5A4AC45A9682D4E699E9876BF7CF9A289EA0F6CE572FB2DF712C54C78735DAEE5AA413F808B933E82240B2BD937298A6196B88A70EE8CF4FD8C5710F898F61C186476D4BB77457FA71FC3278D7CC93100E124F597322570192D14EE1FCB65B56879A2CB8F7BF784AE8D1A66330BD9257443BED893F38044024A4D264251FB9214DE3B4378D4EC413644D751E1C96B3202DC79643164DF41E2591110F85C1F825E47185F43919F3391730E120187E4AE521CC75320970CB99CDC60E0C6FD9B04D33978683AAD36ED35B8873420D2C0297F91C4174F0D6B448EFF31286FB6ACD30E71C6B40C33084B5B9BC1EDFA241294E7841A7C03116922A2DC332D41284C0A0C2D85D89358061E560D4F634B0A9145358BBC9059D1B51E91F959E25815AB851F676A2C868D8E1A4C52CB6250348965DCE3DD13ECA9BCE89ACFC748559879AD070ABE3489024FA8EC9009232C7C858A22D559FA421F19FBDED910DCB6209E45062D1454B51DDBFF1F078A4532539C43019C7866ABBD5DFBBFF8DE61140F9E59A46ED3B91F83E1F2A20AFEF525E9BBA573A2D8B8944E9F350635232426A902329F805E590A49A44B011FBD34364378A123D086989F69639E77B968EFCADEBAAA00E59790681F624F8E32D7D31070430C5B0775BD9CC1D2CD4C0FEB8961B0D3E6465F3AF9EC89802B56C748BD4971CAC0AB14889B952AB25047215AC93DDABAE2DDA4276CC9EC74F732381608A302CD1AD95BC47384DFC13592DD5795544B8FA092A176C70D1148F176C8B1780159D4B1E2F9B264731C74BE745B3499DFB3D8980588E66C548136513CDDC14731FF59256DCFB696772A1480063BFB31B158B17862D0C913F0949C0848A84AAD08E456C71B4A9912242991168BEFC1B788E1CFAC0D29402750CE40895B536ACE81BA85AE7490FE43D2EDBD1D0F3261463612096992D5D8304F5F50BACB94447CDB617E62588916365C1123323F52C53D9DB7A8A1DA4A21F90A9BA345838285A43BBD1AB43472DF326437B8F619CAA6CAA6698FA223B1777F64FAC391FA387B4807A23AF218309F952E93C6F60108781003EEBA89F522FE0ADB82C1128EC57DA4E652A462FA7745A483A3195B8EEF2ACDB16B123418FEB3587490104861F0AF6E1C8A66A9FE3FB002EA26C3BF4D4A6577F651A50D15F53870305FDC40C610282DB9741A5E062D91F7E70034B4D5285D9EABD5426FF6DA4F4CE4F50772A0A61D98309A6F2CADCB76985FC00A570DAA0770207AA76D0B9988816C1FF5E1AC4B8C4169042916EEB683DE186DD390C49946C5A02CF5810888A369FF41FB1EAB5D28D1DC1A2AC0314D81EB5363C97D2FB6267D26D34A3658E66517CDB1F0BA512338D0C6374473C004FFF464A882810D97FCEE3669B6407F8CFEC4947237AF003CADB48E1981D3884E2C5907054F2D32B9E797F2C1BD271DDD030DF634E45D777510399D91EA105523B30C917407220F67B210C4D8EAF1CAFCE19AAAD17B8C803D9FA03A0241CD392083C0B517F3B5C17A1ACF7FC570985C303A562722AA0AF0800C633559D5801A0A45AFD4C4AFB1D93237A3870916FEA1BC05C34678A42F00BEB1BA03727C9BE887E7AAD67DBD192A31E6FD204E86922CFB3A27926955B96AF2AD03F781CB29A483C143E36A9ACA2C9F8354CAA89E96E9F03958E5BBB905E037A1513B0C90F7B9EE55E0CEDAC0D3A078AD5BE57EDC90FD10C2164D2EB5F0A8D92E7EA3217930E6648B0C48C23BDE188E584F48024DE64BF7A3498528EF0F105C6601A9810D4887D98FB940B27458A0A3D10C7808C778680401BF9B3CA4D40FB00582E6701311DC3B3FE34C5D28C5E27FA0581D8372246D253618B37602ED05B360DDDB03B1F29E30AC7D35BA507B75B1CE915D6C3A8567B01885C394CE083C213E977D06F8C0E34B5EBDB3CCD0D30849F3FC3EF00316AFF02DE794366FD1E51BE8C68142C8E673F8EF7E7C3357C042CA017E97EFCE31759AF3431CFBB9E5618383B259FF449A577B18C1475D22184BE92171A3D3B479B2279269856D6308E11341FFAC0294875F4A25A6616002AEA328BD9E17970EC589C7EB5BBEC2E57F94962B6455407727B59187126D2C84A17706429693885EFB98302680E2C4B391E6C5FBF14B0E1626E1914E3DAA24F365179A8B41AAE02C44A03883EE6F2F615286B47F0282B82764EEAC83CE825B704079B4A4F608DF52D5BC5BE0EDA7CD030402E4485569B8B4C6AF9EE25C7A6FB78A28985F0AA2964828FE648C60A1C44EBFEC99BF514A75CF170CF5CBA2153199E124B8CAA5091616CA3084E01AF0FF18C3309FBF9C0C52E76B5C368B5767FBEB09E872ABC9420D10ACCCD679C02DCF86726EF0D348FEB9B4BCACA463E61DE6B20D7AC1C3ED649704D87A56018ABDCE091A714B88F88C3F4E8F18020AC56F1357C0535615C3C94B8D912E81ED5D77F2CE121366B96F5290BDE36B7FD8FF01A4BC0A0005EE4163828B9CE06542DC11DC0108C5EAF4598CD07D6F05157A04FA7A79488EB6D429CF1BF6A45218C93A1DFDFA016900C2EAC81840906C512EDFD1828C7146D14E9993EB14A4EB3F6350D27B502D4B0D65547A28DA8C0BB46D947954CF8DEAC0AC65FC2175EEF882A4344559E4EB3A22FF081BFC52CD8110B8A2C0C9AEF20A7D89386888F6C061CA6B5F546B8F2D84FC42335BD6B0BDE86E6517FE1EBFCD5E84E6226770C41A903BF094D1F32A1C48973B0E257C281B07C2850B85B20491EEA77483EE11A357A182D9581888BC358834D349D1B77BC33C9D8F5993CDED3F3A44195B6CF18E3244E18C6AA9284EA677FB25B1363848D2545A7DE701A7DF13E59FC857B973B24469643855A0B4CF1104F9CA9A51DBD1596069E2CDD50E1536E0086441958BF43C3CDB51C236D36C9A1C64AD34DA5CC4AF4DC562CDDDB34100398F4BFD5E46E1A163ECB2B3905D28B4F2A32367AF138BE4C2AADDD341A4D1313958DD26E0FDF136EF84A60314C1530D079B453750CEA10E26D0696457CD5E7B601160C55DC0598CDA15EC094CD07A986E04D69C4464AFC7154049F620428F63088B0ED525242CDE9F6D116A7D150E6A04A51BAA36AC8EB33124849B255FB3AEFF612494AD0652208C2490F0A42FA55DD3CDEA6566A8074DD783226E7308A15350B80288104250A46F8351F3BF5ED1649DBC1F6CEAA9548BC0CAA19AA09B43562DBFCD4258F72B37643CCD8F9766C994D6A15966DF8BC281C942BDD8F94DC038C30F639A77FC2CA67D723A82ED02C38A3ADBC13CFBC403465698C90952CFCD8D0911FE18807FEABE9FC07D21D8C4C0A15B2A13061A282C64EB91945132D0DB70966273E20A0110768717BEC389E8921864FED429326935B960262880C0759478FCE67B3A6B3AA09839863102EF31D72A4840141632B75401C152FEC0C9B7C87088D67F70B339384970618F584325A238AA52034A11D4E2185182B5E4F778E02BC16279CC56BFBA0B93DC30ECAC64C5F121D4EFDDA6F36491F4E8BBFDE0AE82007BEC87AA960F0F4CC51C645CCF35DE6640F8B18A9BEC0497948BE1B3AAFC8B5F9DC6138297CC9B47DAD38DB0379AD9B5F4E289C20F1D34010CBEB012BFBF4CCA7322A20D340FC9B96C1C50F381D5565FD680A2F91169ACCC136B243E9F100BA3B2AD484D3CDEB999B3E686194FA19EC6A4BB71883BDC965E6C94ABCB2AD4EE1F3302C2968D7794424DF8ED0862AD4F59C99B2F9678A623FF2023FE2424250E9519C3C095FC83B6206CF44AA8207407AB5A2364ED9BB0904AD53A3B12F467C0C61319C8B85A7BC10D6E2C30C10F271A7C254FDC0729018B16B734039B7C9353692416DB1FDEAE1F6BD6116021BC884B10C1E8F4B1783451D8218AB372BEB89762D127372F8E276344119B24F35B04648A755B0AB4DCB2B920F6CA7E2C4D4C9D985F79ACA6D7A09A154FCB4393D4ACC949E86D21DC8A5B60A1E7F2AF3B5984D3CF8238EBB4C099321B0AE61BFF564CA411C2324A64491B88442916D6BC5E5F92AA5E445BBC9F379DFAAD01CABB1CD16D3FA9A0DAE3360817B3306FE07399985C0184E2EB6985177433BCFBDC42E08C90E8EC11C5033A1D9294EBB0B8D34E6668E1422046E6A3413EEA78344FA0DE3E929C739B5EA75DE9ADC43E5AC5C75DEC03679FDC85434A38B981C8BCF1CD53006D69416CADFB1C0E89AE314291E50740A289A0D035850FB0CFE2AD082FF6C45A572F549CEA96142C2D06855DC18CA64FA80FE0BFB9C323CEA8EEB8887652E2926732BCC85D1874A58886C6955322966F1B5655648309E3B2A26105CC819A33ED2477CAFF9B83271E50221073828070A4F3E33CF919B64469E0B8A893D24DAB1762EEEF8CA43C64027FC5E32D701C9736C9CC6CAB1269493D17B2875E4AEC2CAA5ADB052D341F5820E31E6940C0983560A5CDD816C2907004D2FFFEAEAA5B1E5F08A615FE1FCEEAFC136051E40E360D15C10B01D3781CD684D5EE9BC642D07CE4361C7F588DAC40CC6B80485632E9C1A46BCCCC4D2CFD39AA00B34E293429831BBD56C2430EE5614B96A5C036329451001396757D59204DC2EAFB5DE7BBFA09B4574E09D96F8E4CC4387E97075B436B7B1A125AEFB483ECD818596FC393AE6C8E9319D8CA5A6B55C64BB4351A8462E3F7050A2068AB58BA7D101D25822E70D3FA4FC4EBB5EE373874E400313531DF74A84C8239C464C53C334C359B34025CFDC4743FF229AF5391A6CE1D928BBBFB7FA1245902A2D4024B04736D838310254786D33BC97D42317743DE29E15971DA694059280C8EF92D80AF930A67B529DF55799CC0CAFAFF440DB8EE20B05B62E92FF447786F393416ADE380F2F7448C26CB5245DC51D3D2301AA12BBA23DDFCA473030F0EFA1CEA7764BBB5DB7212C4BAF1ED33501D6260CFEDE17A65291C1A87950E0279690E26472321AF5A9EFC5420AB4B860475C9DA1DE8F981CDFC79AFA684908D00F83D3E3CD1F07C9DB029C842796085D0663F4CDB373FF6A08002588EB1F77FFF4B9CC1A010289C9FDAF2A4DFE46F873562D26377F8B38104496698C6BF788F5A209A991B1ED84655462E3C8D29B83A99081C59FB3FADE0E9C75089D64E0DC93E8A58E95E4338544A89EC42AE145A7D95F9F590C8250F5D3F96ECC242B1870DB664A26BDA04E1DEEEC8C1BC44802F1D5C7434BC24759991C272836CFD0552B422AACEDFFC38C1A0F605E900903950136F97EAB802E392BE90451F25D04806B835549B113F9D6CCC88CF63E9F0872AA74FFE918D79EE2A2FAC79214E59023CEAA90E5EE5A34EDB93B9829DD672D1E3CA3BB159265BE00282183F28231309AAD2066FD6B32F0FC759980C905C50E2CB70546CFD876810490A40C29301C29A2C4DB2494D2DD5E3FC23E6AA8DFBDD820C41B1EF24C815A140A0B5950E22DB2BD7D74018A7C62C72150C95BD1AF7A5E88D244D5292102D6BF9719F9A6ED97B007DAAAC0B957110A4857B0FEAB94A0AC86A1A1978D4FA78E093238CD2C1C703FF256741F6F88151AC4B0DFCE627FD95D296019254147E5171BB3E59E6820D03F1B00B8116A295E7791B418D459DD57A21CC86500252DC8AB5B54DEB4CC7EB095FEF08AF51164708C54246BB912DA8EC424A6F853D0219D41C281C75C80A5258809DFEEBA702392F79E22100345046B86A17877B25E6DC2F8ABC1C53518247C0DA07B9D62C6C8308D61102C07E9D8B6CF3500A2830E186768D5385BD412BD0E3813026673FAA80DF47DEA18A8428FC1C266F30E93BBC2EAA32984189D249EBFB1312BC033530156DFF0C245B224E8F9A23A16AB089F8CAC35740665A2CE53847C559721D2903E9985D4093C6BE582054551422099DDC250581ECCBCDF9A89A062A1763214ABDA2A15E61B822E6BD7EB38998AC120E158233C34D2123A304111BB015A45C33D8F0C5DA622E35F955C0CECCBF9518421932870B2DDA10E6D2B3521D43260D8A19478C7DD0FFF96A51466B2E2701AD14E6D93667E61E78F1B89B3CC06515DADC48D46786742A71F9EB99B32A0FD8191EFAB70BEDBA9A07A8BD95FA35E162F5E95BB52666455C7214DF84BBE5EBD6285EC904D7F1F8B5B43EB7137FFFFA484CE26EF63DAA836137EB886B873514B84CA4708C7AE3F2973D6F2AF0246B99C930110CA9D456E99CDF275981575BBC2CCEE86956D971FF8DE8099BECE101DDF2772C266C2C5F2FB010528B21566B6CF0E8160DD495D6104CFA36448FD077124724C9C084A737093ED6ADEDD846F2B640F426C5D10E30598BF08D70CD114B8C8CCDFF36EED37599E6DD630C6AB7A28B23C55613AC4484A1C53D5B911A570F6E2F45122840929CA2E1BF0654ED678009B835F3425110E3EF2B5D746DB38024A0422BF2468F8F995C02DB9464E7E7D3CF71D677ACC109C5CF90DDBB58D2A096D30487B61E8659675E97C959CA822C92D872102CC6B90608CEE167D9B132D23429BEBCCA3A46301DD7415474A90F274406BA79343AA36A1DE8340C9402D4E519441D861478F0C5C3AE06C2A0CFB50436C473E2C2E2DB86A5828CC32884022D13E8CD42C09FCE8AE291228224FB5052F28F5A70EE2419E4A596E15236F10C36FFC09ACAA7E497BDB1C1293F2C7C3225D07969761B60198CB4C662A8E50EF8F5D8B9A03E7F93DF84C727A3A005D82ABB842124C87B9DD25800525B5E6748D5CC3932E8EDD092466201C6121A55F3090FCC3F81DFA48341BFF0A3AF4A3B38E73413E979C14AE201640E7E5EF7191037900B2331710A9AF82C5FD70CF9AA49168004531033593684FABD0190A64CFD2B458A9FD4B07E54BA1CE053F093841C84736BC28C5C4E050AFD58521FF9EA090C040B3ABE2DD4AAAD0C197921D03C3543889B5D1C7AA9850236A3B4DA07EC2FBDC309E058035CF5241D239BF090491028C1983A3560A93B028017D1E9538B688B487D0ADA40D0B71B6BED780A9EEFAB9402DB6B21644FEEE7000664F1AC6BE81900873C92345F50651C15901B41CD0A5BF5C742ED5D96F651582A2898A350198525AA32197A3C232A9B383DA9DD8C81567AAAADE83C66487DD6F24819178CD280252CC1AB5CBE515D2FC757AA9BABAB3A4F45AB0CB8682E86D3DBFA6084A5B77E78D17810ACBB55ACCD7EE74FBE624EA81E8B1804BEF83E426B76E004B228CA84DF4106DC33629D5CD45D4E345ED466FD3EB9E35E0F20D96FD6652DC6BEB8AE21BF46A1873BB6512FE102CB825FA4DA430A641045AD787B770006256DC8E845B33EB29FB82AB6D96D988FB428A25C61639272C261F2EC32AB60DE093C66E71D73CA586BA027DAE901839AF404EF7E01E4D2A30148DA9802C2C91DA78A02B41423BBBB011C29790ADC46FD4A488D23B8D473A21876B204ECEE0A50AC8EB5403973888265090F4A6F6F6ACB489CFB2173154928B19C849596F815B06EA270318CE585BF748BD8E1218090107B9887F83AF018B6A4E791247042C208D71A49EEE71620BACC8831398C302C752F95E4C79BAB3709323C04036068797532393509B6ACF47ACFD5F2DE0684C13C7824D6FC8984F25400A0E437AAA3F3556909B093709F5618C3301DAA5F52F2F3202F34083C248031CB88E54E138427A863959135C7F61E95A8BB0937B646F27B471772B78200E2BDB77CA6F5D373C2A7BA08EFAD330AC8A08C4810A1AD52E6033C738B15E3B471A9C550E26061A6947B2B1B36945F85A205C20B25CF4730064A1B05C5CF1D4453498271D560E7CA4F3868BD279CE2743464B58F38480F1B9728220630D4F09E09C32BFD7AACF46F0C730DCDDACE8FF2C781A929A59C0AF4E237050F418637353666B90B574F901686C18088990E2BCC1424327800C630641C315D89E2DD2FB6ABC7385077E1BBDCAB92C20FB27B8D3D1A34E25284C8EBAC0C00CA0791A9F443C53BC13FCC8C3C1E1F40E12BAFA69E1661E17F58B4F05362511955DD23ACD62A3ACCDBF800CB1CCD8785EE66CA60514DB48D6EC5CB9E3E64ADBBA43FEE5AB077DCDE6D295796330BCF3FA9A0D58A437561CC9C00D6B3230418CA4D5E82231C07624495EA8AA899DC8F42E5E99CA542ECCFAFBBF2E8149C824E466E40B35141F07332600C98DE345C3DED7EC0BB3437ABB9E14428CDD5E3DA0CB3FFA21007921CBF1F32B04E7372D4AE4F83D557887D2678FD6229150F64E1DC21B34EBA49A2BA7C3F02D586E53645828E1AE7E42FC9931D1FC61D391ECDCEB3EA0A48ACD2A6BD6CC69481D76D024BD139041E16AF196BF3095A59C79ECA55DA7EDEB74C99AE836BC4CCD81F632C6258006CDE3BE0AFAC92386BC841C2291C7915BFE630E1D3124F4EB90CA4FE87CE8507BE49394C7AFF19E33E0CCEA628477B461641224D3B05931128C4D34C57E65F2B8D78E050DE20CF506B1EA44E345250A5267A0A61576484A8E92E43B4306AB2F3E85646BB386019516AAE9927EDADEF34E29C0390AC4203EBA0488B3624A34FD9927B31B5C606170EB94E83A91BB284E1C8CDCB0F4B360200AF4B659943D388653F85C9352AE19488DB79A2FE1305E2BB8FD331A397E1985743E789E4C63912E06B14B66C82285AE4357FACEA39E7F82083EA60194CDB07A7C7700804527712F4708BFB430C38C102BA6BF437C608A61AECFEC2C5AFE516E0C9941884DBBD1BCC3165121209025A39429A312A03C1DCD1A69B0DAFFA9246FA3F869B62975FC8601162B240F677CF56C2158BD5A9D835EB8E07D1A8DA24A7EA24723223B56543DDB4B42BAC5CCD94AE87333058CE939E590599DF0F85A3BF02B02B67EB303C59FB86195034D560E225E70F5E55B4F43386004D9CE6AE430C3AD91D76C1CB4315680CF71C7F2B4B86068E225CE232342E9FC1AD173721A3CB94FB6C34F5E9E00A68DB4D3B597F092020E51BF4F5BA6C434450D896274AC2A021C675DF5E43868169573B0BCABC9B7C79C781F0AC0C108B18773B1EDA20E8583E59048E5D4A603310C94687BAB4FFED461E045A4BC3A3B15744A5188337A98555C70E11F018DC6CC6DD4A3EBB5BF00CA901B7A207E6C021C66DB8926D09F9151E800878979D15A2ED571750222E095A4A1546BF319561639E988981F5513410CCC60EF95C5C6768D83F980423433E0A4322458E6B007F43AB7342969B99790F094BEFDC2DE15FA1871E494B503802BAC8CB842563182321621C210975CEC766A0FFC308D808E5BD8D56666192144621A208AA516A71D3F9C2CEF6F02511900229B46DF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BC"));
-        byte[] sk = Hex.decode("07FEFF0702FB0106EFFCF40401F9FA0B03FF04FDF2FA0F0201F40810FC02FB0D140204050CF617FF00FD0805FDFB060007F2F2F6FA08FB05080504FF0801F306EEF5F8F607070202FDF9F3F6FAF5FF0703FCF1FBFD05FBFE03FBFD0509EFFF090BFFF903F20002F309FE0302F7F3050906FE110207F5F1060DEDFAF909FBFC1502FD010707FBFEF3FD03000A0301FCFAFAF50500FE06F50D030008F1FA130006F804030AFEFF08F5050800F603F60700FB08F80D0AFC06FF0FFF01FB00F7FE05FEF9F60301FBFE0A04F9FD05F402FC00FF09F70EFDFEF7F10808FB01FD030203F3FE0EF8F5080207FFF30D0201080A03EEF60104FC02FEF9FA0A02021000040705FEFF03FD06020009FD05FC06050204020A0D08FAF9F1050DFB07080A0405FC09FD1003090E0C000703060204FBF806000AFDF7F4FE0A0104FF04FBF6FA02F7EDFDEF050710EC0B0209F90600FF0703070509FA0CFCF9FC0304F6FFFA03F3FE0202FBFF0812FD1409FE02FBF5F80B08FF07FDFF06050209F609F90BF6FC0013EF020E0F0B04F4FA050DFF0BF8F5EF0AFCFEFBF70512EF10F505FAF7FFF9FCF7FAFDFC0808FE0A030CF8F50C02FD03F108040612F80214F6FC030C00F30B0EFA00FDF80C000603F809FF0304040AFEF807F60B05F5FAF6FBFA0A0A12FE0007FE0AFC06FE040606FC08F9FFF6FE0AF6FB04F6FB060C00FAFCFDFE0800FBFB05F7F20004FDF6FD02FC0210FB040DF604FC06EFFCFAFBF7F6F9F003FCF9FB050801F900FE0A02FF09FA0203F1F900FA06F501FA10FAF806FCFA03000908FC020AFCF70102F9FE05F202F2FF0900F702F4F1FE06FAFBFD08040705FFE9FCF90907FDFAF00F010B010B02F4FFF90FFB0401FF05FE08FF02F3FDF3FFFF10FB000FFF07030B0205020D0B01040BFBFFF9F71AF00306FEF704F3020AFE03F0FE1801000AFBFAF7FEF7EB03FB08060A020109FC0E0107FB020BF80506FA0CFD0116F603070E0808040AF60808FB04F903FEF1020204FB060FEBFD04FF07F8040708F807FEFCF9020A0A00F6F80A0D01FB02F5FEF40908F401FF02F8F50B060205FC11FA0404FBFF0305FF01FD0303FC0DFF030A0AFD0009FD06FB03010BFE0B0A0D0003F3FA07EEFF070008F7090206EDFAF9040709F50D07FEF50EF9F7FFF8040208FB03F6FDFEEB06F5EF03FCF506110200F6000D030603F3FCFB0E0EFFFA02F504030B090B02F90806FE0D04FC06F6FE04F90607170106FEFCF6FA05FBF0F6030600F4EF03FAF802F30004F30703F5F8FD05FEF0ECF9FC0209F906F5FA05070CF60FF709FEFAFB0D0305FF0102F807F703F0F40D0204FD02FDF108F80D0CF7FAFA070503FB0307FDFAFEFE08FA02F80D0B0AF7F710FC03FBFDEF07F9FD010104F1FEF1F4090507FA1101ED0B050212F80E02FE000708F5FFF202F500F7040DF303F3FEF4FC0D050105FFFF0A01F4F6FFFCF6FA0001F908FFF3F3F8060AFD150107FBF3FFFB05FA08F6000DFD11EEF9FEFF000CF600FA020204F70F0909FFF3FBFD0906F7F5060512020803FC0001FF0C05F8FEFBFBFEFE05F90307FEF7EA081105070102FAF706FFF41407FEF901F806FCF105030F0100FDFA01010807FFFB07F50DF4FBF70D01F805FAFCFF000AE6FE0D00F80AFB0007000009F8F8EEF3FA13FBFCFD05F9F503040EF608050CFC0E060304FBF80E00080901F7050C0007F9F009FCFD0001F9FB180EFFF9EFF400FA0206FE05FE06F6FEFE00F207F0F9030A0AFF00F6FE01FE0C090404F7F0FF04060F0B0806FDFDF6FBFBF2FFF4FAF80CF9FFFAFFFE000703060BF900F80A01FEFFFF0201FA00FE0BEC03F7FA00F8F60C040300F6FC03FC000014FD09F80203F7FC0A0D0007F806EFFF0409FF0007FF06ED0A0A02F3F1EF0403F8070405EF0102EA06FA0206000308FA0205010FFFFCF6EF0C16FAF7FA010DFD0503150EF903060803040004FD01FE010607FC15020808020406FDF5F304F7F8F6060AF7F7030CFC01050404FB01F8F8080905F70607060AF3F303F50501031200F502001205FE01030503FF0400F2F2080AF7F9F8020DFFF5FF03F8070806FB06020704EF02FD0C0108F90808FF0109F70BFC0BFFF30801130A09080202040EFFF504F5010704050A01EDFB0DFB1109FA0605FB030CFD01FC1601FAF9FD09FF0CFFFDFCFCF5F8F50606F9F8060102FBFFFBFB080CF913FB0409F9F90C0EFCF8F9FDFEF602F4FCFDFC040902050AF8FCF9FEF40C0A04100701F60E0901FC0200FF08FAFFFA05F70803FC0E0707F9010A0600FB010CFA06FD010EFEF7F7030011F202FFF5FFF20706F7FD03F50205F70500FAFFF705EEF7080C0A03020C0001000702FE02000404FC0105F0F7FFF0F7F6FB020DFEFBFDF0FF07FD090202FE080201000B0409070AF807020EF402FE1003FE02061202F8FF02F200EBF90BF8FDFBFF04000704FF0306F7F3FA04FD04FE0CFDF80BF804020501F3000403FAF8F704FEF80FFEFF06FCFA0109FE0AF200FC05F3FEF90701F40E00F1F8FD020BFA0B09ECF2FC090800FD05F109FAF708EB0C0D01F30AFD0B06F70004F9FF0902F604FAFC02FFFB02F5F50900FFF811FCFFFF09041402F5F604FC07FE01F4FD02F0FAFC0005FCF8FD06F80003FDF9FD0EF7FDF907F5FF07030B03FDFE00FE000F000A0D0303031508FAFA07FD0314FF0CFA0B0206FAFCFA02FE01F7FB03F70004F7F409FB04FFFE0601FF0AFA05F90401F51005020A08FF0A0CF805F5FF02FF0501F503F8FCFD000403F5FDF1F805EDFEF9FD0B04000008FF07FB0C14F8F9FB08F8FFFA05EFFBFF0002FDFAF2F802F8FB1A0202FC0010FBFBFEF9F705FDFCFA07FE14FA010D070B0105000008FCEF04F6FD0B04F9FFF00DFC0A050608F9FAFAFE00F7030BF50BFD10F5FA0F0100080C10FD0600FBF30A0A0CF4FF01F20A0E0D0CF4150000FC00FC00FF0E07FCFAF90CFDFDFCFDFCFEFCFEEE0A03FC07F80205F902F60A0208F3F7FEF90202FD00030004F2FAF903FBF80103060FFFF9EFFF01F412EC0A0105F901FF0E0A03080202F6FF09FBF209FA0DE805F4FBF506060110FC00050FF5EFF50A0BFEFF04FE14F3FF020EF802020508F5F9FEF90806FCFD050E0711070D01FA060205FF0204EFFEFDFB07FEFD00F90B030508F6EF02FE16F400FDFB060304F701F3F4F90501FB03F8F404E5FF02FB0A00F8FE0C0B000AFBFCFBFF00FAF2F5FFEB05F0FA05F5FFFA0408FB00F90204FF01FEF00C12FF01F8F6070402000502FCFC0B04010E021103FF0BEF09EAFDFB06FDF9FA080CFF11F90AFCFAFFFE0301030001EE0EF3FF0000F40FFEFB08EA00000F08FBFD10F307F1F5F200050105F9FB0AFD031302F4FFEEFE00F709080BF7FD06FE0EF5EB010DFD0607F707FA08EE08FB0007FF08010102FA00FB00000803FD04F806020500F8060CF4F9060EF9FE020A05FB0CFC0006070202F6F20003FD0DF106FA0206F10304FDF80F00FDF6EEFFFDFE0FEBFC0D1BFB05FDFF02FA0806F7FCF1FAFD0403FF06F80E01FBFBFC0C02F203F90B0B02FA0CF815FFFE080201FB16FA02040802F50503FDFFF8F7FBFD020202F8060D0601FC04F1030202F30FFEFCF50BF60901F306FE0605F9F8F502020A0406FAF80208FDF8F9FCFFFD0EF30B04FFF5FD040001F8040105010100FE0E00FDFB0606FA04F505F9F811040607FD080500FA07F300FFFBFEF0F5FD0209F802FF04FFF9FCF9FF0AFBF800F80AF9FC09F9F6FB0EF80805130B06FFFF0104F40603F10001FAFD06EDE5F8F90C05FF1202FEF000EA0006F801FFFAF1130CFDF9FCF806F913FFFE120609FAFB06000CFBF804F40BF4FCEC00FEFD01FB0307111206F9F1FF06FE020CFDF802FC0003F9FEFE06FEFBFBFB0BF8FB0C04030BFB00FC0405F8FD16FBFF0207EA08FEF5FA0F07F50506FE00F3F20BFC0A07F702080FF509060203FD0505FD0BF6F905021206FAFDFF03FFFC01F20204FEF6010009FF0BFDFC0C120402FF0602FC0106F40601FDF6010B0611FFFFFFFFF8F5F7FEFCF5FB02F805F50105FEFF0202FF09EC0408FC03EDFA00F306FCF8FD0301FBFA08F8FD0007FCFFF6061011F80E090D07050506FAF00AF8FFF40304FE020AFCFF0CFFFB0BFD0E0C05F50C02F60EFEFDEDFF090802FE09000BFF06F208FAF9FF07F500050901F508F1EC0BF9F6F30FF8F006F6F50007F107EBFFF80406FA090401FBF812F608F4FB09FB0FF8F0F1EEFBFE050011FF0AF7FDF201070C050EFEF5EFFD01FF08F500F3090BFCF201FEF90BFA0DEE01FDF8F5040109FA03FE0601F30D02F904FEF804FE070700FB12FAFDF9FA13030A02FAFFF60407FA02FCFEF3010E04FC04FAFCF4F7F802090307070F03F808F911F4FBF004FCF704FB0B13F8030B01F307FBF405FFF30105FAFC07040605FB0006F7031001FE120600FB02FD08FFF60C0709050108FC0CEEF41AF40DFD02FAF50AF6FEF10000F2FEFDF0F6FBF9F30BEE0D0E0105F6FF01F105070E06FEFF0713FBEBF8FF06F50100F504FDFBFD080AF9F6EDF9EBF60B09FE0C00FDFDFE00F51EF9050E0A030101F70A01FF02071BF2FEFCFF040309FA03FBFEFC14FCFDF6FC01FB0AFF020206F6FA0D11F7090BF9FCFD07FC080709FE06F4FDF5FDFBFF01FA0F0607FC02080601FF0506F7010808030006FCF8080F0200F2EF0AFF03EDFB03EEFA02F00AFFF80DFA11FDFA0FF801010BEE0103FA02F5FE03010704FCF6FEFF07FC0901FEFFFDFAFDFCF002EF02080506091208FA0B0AFB0116FD00FE0416FE01F70B0DF60110FF0608F60507F701F6FF06F3FFFE0BFCFCFBFBF80204FDFF11F307010301F6FAF003FE050704FD02FFFAF503000103F0FDFF1006060EF1FAFE00F8FE0A08FC02F8FA051406F508F8080306FEF8FE0F0108001305030DEE07060608FF07FE1107FDF80105F20A10FA03010C02FAFEEDF3FBFCFE060104FCFF01FF06F6F5FDF4FD0904F702FB0EF6FEFE01F8FAFB0003FBFB01F915F407FDFDFA01F9FC0A000807F5F8F4FDEFFE09050B0BFCF9FA030103F2FDF907FEFAF70502000FF50605FCFDFE0A05FDF5000201FE0EFAF613F9FB000404EF00FE0D04F901FBF60CF7FFFD0203F8FDF70301F2EB020405FFFD03FB09020405F5FD06FD10FD06F5F5F808090404FFFDFE08F9EFFF05040CF5F412FF03F4EE0308001007EF0BEE05FAF80FFA02080807F90001F8020906F4F2FAF901F501060800FC0306081601FAFD07010EF606FD05F9F305FDF504FDFF060F0606110A0002FA030007F80E040BFFFF070100F6050008FB05F8EEF70005FC0409FE0007FFFA1000FE08FD030B0104030D0106F8F9F4FA0901F8F7FEFE000D0FEB08FF020B0605010B010EFDF500F6FB02F6F8070000030AF6FC0702FAF90DFDF80009F901060B07FEFDF002F206F109000DFDF1FCFCFBEF07F7F307F608FD00F803F809F9010408FCFFF807FD00FC0DFDEDF90706FFF7FF0DFA02FA00F60000100205050B00F602FFFBF70BF60001000A09060A0A0A05F7FD01EE0EF8020BF5F006070AFD040B01F2040EF0F60AF90BFC03F9F102FDF50000F3FB090FFC0705F209090304FFFA07FDFA02060507F705EFFF0BFA01F2FBFDF503FEF5050E01F908F406030005FDFC1603F601FE00FE03F407F50308FEFF0403F90204F509F1FDFD0503FB05090802070D07F9F8EE050E0107FB00FB03FA07FBEE0012F310E4FF04FE000C01FDFF0206FBFDFEFD0802F6FC030702FB0203F7FE00FAF6F8ECF5010DFE00040BF3FF05FD0807F2FFF9FEF9FB08F7F7F6FBFE010708EEFD04FEFFED0209F703EF07FB0AEC020BFF060AFC040308FE030D05F109120CF80001F4F708FDFDFCF80B00F203F8F6040203020010030BF9FAF9FFF404F6F9FBFDF6FEF8F7FC01F2FCF8F3FFFFFCF7F20B0F0A00FBFCFD0408F50DFEFF08F504ECFD13FD10FD08FD05FF06F70407000C1005FAF7FBFBF304050DEC0101FF0CF1060010FDF9FF0705F404EF0307F9000106F801FB06F802F8FF080C0CECFE07F5F4FD06FCFC0401F7FE050EF3F9FE03FCF10302FF08040907000104F90303FE11F80602F5F70201FDFA060FFD03F40A1205F205070AFDFC0A0002FE01F902FFECF9000D00060EF0FB0D06EBF50C02F614F4080402FB000704FBF706020814FF0A01FC0A0E00F0F8F607F8F608F1050C04060213030BF60606F9040C03FBFFF3120403FDE70003F4010EEB090001F40903F8F9F5EF06110D08FD03FF0408FDF50D0407F9070201F9FEF4140F04000A07FA08FEE907030A05F6F8F4F7FC09F502FAF808F6F6FF06FA0100FF0B0D03ECF50AF7F0FAFD0A040901FD0FEFF70BFDF104F600FDF5F1F80DED0D070009F907EDEDEFF201F90B03F90D0301F5F706F402F5F7FFFF0AFBFFFAFF08F9FEF5120405FEF808FF05010A0AF9FD0DFDFAFAFBFD01EAF6FE0BF3F7080306F60D000403FEF90AFB03F70D06F907FDF811FDF806F707ED03FE0900F508FF0C07F704130805F6FA0300FCF900F804FCFB010309FAF9FE04F505F501FEFB00F4FA0A0500FF020A03F90EFFF80305F8FCFC03F20408F5FE00FA0009FAF80EFD0A0311FBFF0D00F9060505F60504FFFEFEFE0AEE0A010EFCF701FFF80302F0F9FCFDF5011209F8F80500F4EC090CFD0601F612FEFB0F0201FCF31007F7FCFFFD0F09F01013010707FB020307020402F9FF00FB00000E050600F7FAFB02FAFE0CFFEE050E020D07010304FFFCF9FB1101060907F606FCFAF9FFF90408FAFE080DF8F7040C07EFFCF7FBFE0CFCF40DF908F0FFF80803FD13FE000208FC0EF6040AF1FD01F2FDFF0800F80FFD0FF807FFF9FB01FAF8F9FCF8F7030CF7FC0305FE04010003080AFC0300F5FE04060AFAF114FFFDF5FFFFF70AF7170416FE0AF70207FB0AF5FDFA06F3FC10FD01FB040A03F9030707F708070507F308FB0AFA04020404F8EBFA06FCF403FDF7F7FC0D070306F5FA000AF0FDF300010EF8F9FEFEF904F801FBFDFEFE04F2F0FAF8F2F005020FFEF7F9F4040AFF000BFB03FD02F9FDFCF6FD11F815F701F2FF000CF7FF000AFB0F06FF011400FD04FE050209FC03FAF807FBF8FD060201FD07F607FF010E110AF2FBFBFD17F11508FC0202FA06F90B0A0602EF02FCF0F203F4F8FFFDEEFBFC0512FF01FB0103FFFCF5FCFB00F703FC0A09FD02F8FDF4FA07FFF404FBFB0CFDFFF1030610F3F6F5010017F501F505F4F603F1FF0A04060209FC010608EEFF05F60604FAF90202FB030502F4FD05F70312FAFF0007FCFB0B020C07090AF9F2F6F8F203F1FBFF000207030411FA01FFFE0303EFFE0606FEF6FD000901F900FD07FD0B00FC050004070DFD050CF50B0D03000104FC0002FCF6FCFFF008050D0002F00CFC0506FA030DF604EEFDF90609E902F5060AF7FA0A0309F90BFE02010E0B0405FFF6FAF80307FD09FFFDFFEE06E8FF090805F5FDF6F90C07F40C04FB0BFA04FBE604040DFFFEEF05ED020108FA00F804FEF00304FBFDF702FB02FAFA06E208040402F2F4080501FDFA00070DFAEF11030F10FDFB08FE0B08090101F1FEF7F5FF02FDF8FA02F8FEF8FAFDF4F5FF0809010108F80106FC0701FA0C0B0DFD050301EFEFF7FE04070A050C0BF6F308FC06090207FF0AF4FF09000A020304F5FE0204FC01000101E9FBFFFF090303F5EB1802011204090EF80004F9FEFFECF2FCFCF303F8F505FF07F80700040D08FE08F00006040006F9FAF7000C09F804FFFC00F910EAF8F80700FCF5F6FF0213F8FEFAF8150208FBF80803EE06FAF504EEF50F0800FAFCFFFA02030211F50CF6F8F004FBFC01FD0BF20CF7F60100F7FA05FFEE0507030807F8FCF6FB01FA010600FC05010308F61402F605FCF9FFF40601FCF9F605FF05FE0011F80AF9FC03FD00010B11F8F90802F9FC0600F50A02120501FA11FD03010AFC030C040405FB02FB04F0FAFCFBF700FC10FCF6FC01F301FCF803090DFA18FB0701030DFD02F204080806F106FEE9FDF507FF1505F8F8F8010C030909FD050306F40F08FE120A0C050102F7FBFB000604F8FDF8FEF8FE080AFDFD08FF00F007FC0003000401FDFFFAFC030401F2F6020CFDF80401F7F807FB07020C000C0101FEF6FEFDF60811140E08011407F905FA04F504F60AEDFB0210EFE405FBFFF9F0FB04F9F311F7FAF104F0F6000009F8F5F8F7F303FD0201041A04FC0509050301FE03F109F1F7F9F10311FFF3FB01F8F9F6F6F8F80AFBFDF20AFCFF0CFF01FBF8F4080DF502F201F0F7FC0AEC0713F6F90206F7FEF502FD05F9EB110001F8020706FA02F80105F404FD13F8F5FAF104FB0E0301FCFD04FBFF02F5FDEF03FEFBFD010D0708FFF4F6FEFFFEFD0CFEFD01F8FA1207090001050AF20909F50513FA06F3FBF4F7F40301010307FBF60103FB0B0AEDFC0D08FE09060A03120CFCFC1800F5FA030AF901F2E8F903F201F7050DF8F9FDED06FEF8F0F5060507F5031001FD05F3F008FE0BF9FE03F5000600FA0103FD06F8FDF9090CFDF60CFE050A05FFF8F206FD0904FC0200000106EF07F301FD08F701EDFD0300070F1404F30BF506F4FC09FAFFECF8F7FCFDFAFB07F60AFD0AFD03EE090900F80207F0FE04FE060A0B0603FDF9FC0000FBF3080B07F90904F0F808FC00F9FEFA04FFFE05FDF3F206F8FDFB03FB07FA010CF60DF7010FFEFFF40407F302FEF0F80D011306FE06F905F312FCF10107F3060310FFFDF507FF02F3FEF1FBF700FB060305050803F90703FA0405020BFC02FE03F315FF12F80806F80201FA020E00F9F9F90309FF080D0501FBF512050201FD08010AFBF606F00908FD09FBF30810FBF7FD130109FCEDFBFEF901FC01FC0FFC0403F51A1603F20812EB0404F9FDF700FB08F5EDFF01F108FE030CFCFDE9FF040703FC061100020C04FDEDF9F8FC0A0603FC02FE0AF806FA04050AFB0AF2F4F6FBF4090E060504FF0703FB010401040606F9010C00FC0B080400FBFD0B0C0505FBF3FCFCFEF610040D0808FEF4F001FB000EFF070806FFFCFEF406F9F4F8EFFFFC0605F80805EB0403F91313FF0303FC010206FE06F4F7FF000AFDFBF707FAFE04EE090A08EE0AF8F9FCF7F50C0308020C02F3FB030701FF0D08050BFBFCF9FE1200030704FFFCF9F3F00CF7FDF805F501050302F7F9F3F8F608FEF105FE05EFF6F7FBFF05FE0713FCF80D0B0303FC05020B13FEFEF9F1FD15FFFE01FAF508FAF703FDF81208FFF9F90700F4FDFFFEF502FDF90B0C05070701FE0AFAF9FD0706100007FEFA0DFEFC0BF8FDF6FD0104FB0309F706FC0606F101F9FEFCFB1302FCF4FFFCFA03FCFF07FFFD1509EF13030001EE040105F901F908EF09F7FE05FEF1F60CFEF7F50704EE05F801F70208FD01FEF812FEFAF50102F4EE0102F10906F902FA1316F3F60406EDFAFBF7F5F808FD04F807060701FF09FAF4F604F60002EEFCF2FC13FEFA02FDFA0814030207F6150004F6F501F10708FA0FF7F9FA0108F2F705020101F30E0906F50F0BFAF7EF0907FA0003F4F8040A08FC03FFFAF4F8060605FCF807FB0DFFF6FDFBF7FEF8060F05FFF700F1090907F403EC03F705FB10030E0AFAF7F7FD030AF6F205F603F3FB0AFAFF07F40BFF03FC08FE04FAFA0001F60903F602060DF6FBFF0801EF02F7FE000E0BFEFAFDF506F503FAEAFDF5FEF50C09FFFD00F9F6F809F9FA090102F5FF0000050A0400FD0DFD0705ECEFFFF7030200F2EBFAFA02040E0CFCFBFD00F90600FBEF0609F0FE03FCF9FDFC05FB08F3FB130DFF0F0208FB1006FDFA02F902FB1108FDFFF8FEFD0311FFFA0502FBF905FD0AFEF50C08FC06F1FBF7F40003FD07FF060809FBFEF30A0500F6080BF60D0CFC030707F5F413F903FE040006040A0C0403F612090002050E0AF610FB0401FDECFBF7020DF9030405F609F0F0ED0103050FFD0B070506F808FBF6FB0519020703F808000609F402FF0907F3F9FC0704F7FCF7FBFB0D070AFA0BFCFC130AF804F2FF040105F0F1020408FDFCF9F407F7FBFDF300FBFC050A0E12F4031504FAFD0214FDF103F2FEFC0801FEFEFEF91502F10700FF08FCFB01FD060C01F6FC01FD070107FFEEF010FD0B0208FA00F7040103FF0BF904FEFF02FC07FEFE0007F80305030204FA03F9E8000D02FFF4F4F60408040105F2FC0DFD0504110102FE0F00E706F301F7FF0108F90305FA0202F3FCF612F101FEFBF80E0C09FDFD000013FEF6FEF6FE0BFF03170001EC0501FBF4FDF0F80BFEFD111C02FFFC06F801F8FDF9010BFE09F6050602020BFB0C05F90401ED0FF6060E02E9FFFFF700FE0E0304F2F5F6FBFAFCFD03FCF5040417FDF506FB05000BF8FFF1FE0405F102FEFCFE08040C0F06ECF6F6F9F4FFF9F9FBF8F1FE00FFF40CF4FD07FB0503010B15F3FEF90CEEF80AFF0BFE08FFFAF3FB0AF301FEFDFD01020706FEF5F714F601FD040A0A0D0A0BFF07FA0700EFFDFDFBFC06FDFE00F804F3FF00FF0AFAF9F801050F07FC0310F7FA08FAFF0203FEF6EEEBFF000009F906FA0A0CEEFFF1F7F4FEF3F5F4FFFC10FB080608FFF6F906FCF6FE0D00FF040B02FC0BFF00060D01060A0AF600F50CFFFCFBFFFBF9F8FE070EF5FEF8FBF606FB000DF706EE04FBF0030BF000F3F8FDF002FA070A0603FB02FC01FEFA0407FA00F2FFFDF2F80BF607F70402FC07F211FDF8FF0101FDEDF601F4FBEB04F0FB04FCFD050101080A0AF50302FC0803FE1200FEFDFDF3EE090E050DF9F803EEF1F4F802FCFD11FB02F4FE02040A0C03FF1506F806FEF9FCFE01FCF4FD0104FA02FCF9EC0911FAF2FD06FD03030A05F50A04F4FEF7FF0B050001FB07F80601FEFE0CF9F7EF0C1209FE06FF07FEFA0600FF02080207FFFAF5F407F5FAF500F90303F81106F90BFC050FF900000D07FD02F9090306F90107F407F3F7000007FDFC090003FEFA00F307FDEEF0F9FFF70CFF0BFAF9EB0B040605F70504FA00020CFEFF09FD0CFD03FB00FF0CFE0205FC02FB08FE06FCFF06FD070108FC0DF60E070EF7040D00FF00F1FE0108FBFD090008F7F812F006F7F2F60916F0FD0F02030404010502FEF6F0FBF10BFDFCF3FD0F0FFE08FF00FD02FD0602FE00040CF100010AFCFFF90818F800FC010F0AF60A0403FAFBFF030803080DFDF9FEFC070E0AF30200FBFCFBF913FBF3F9F9F1F7EB0C06FCFF040D01F0FF080009FA0C0AFF0505040008FDF6FE01FDF8FAFD01F9F90708010908FEFAFEFC010C000312040804F6F8FD0406020D0004000405F8F8070EF8F4FC070706F80A05F4F90803FEF3FC00F6F206FA0CFAEB00FC00000AF2FBF705FF0AFFFF0604FF040709040401FE1505F1000EFA030F0BFFFE0602FD03FEF5FF03FBF0000F07FB030000EF01FDFA09F80A0606F804F5EB10F502080606FE0A0703FFFC0308F40806FEFFF9F6FDFBFFF1FD090501FAFB050E0BF9FEF8FFFE00F701FFFAFE04FF0204FE0CFCFF00FCEB0F0D0006070606FB0802FB07FB060306F907EFFFFFF8FA0AFE02FEF70F06F8010100041211020608F505030407F400FEEE09F9F3040D0B00F703FDFD01FA0004050E01FCF801F6010806060308FC0AFE070BF813F9FE0F0EF8F700F708FBFCF2FDFC0DFEF4F7F70BF9F4FEFCFCFC0BFFFE0AFFFFF6FDFAFEF7FD0C1005040AFBFC10F9F907FE0AFB090808050100FCF901FB190FF20C00F8090106000C050AFC0A0201FA0902FE0503F2FAFF06F0FAF6FB050204F905F604F6FCF90005FDFB00FC0B05F1FDFEFB00FEF703F705F8080A12FCFEFA08120001ECFEF7F40FFC05060008FBFFF607FAFDF403FAF616F8F402F9ED1204090A05EB00EEFC10FD03F602FA0FFD010807060504FCFFFFFE0C0BFEF7F7F4010C0AFDFA040EFE0415F702F3F3F905FC0A0102010B05F4FFF7FBFBF701F80EF400FA03F8FEFC08F9F91BFC05FEECF90B06F907F9FBF409FA02F70605F5F6FC09F5FC0BFFFEF007FC0B080001FAEFFA0DFBFEFD0504FD0CFA0205F6F908ECEEF8EFFFFA02FC03020BFFFAFA0E03FE08F400EF0102F8F1FD0002FDEB060AF9060808060712030401F603F5EB01F6F604FBF8F0FDF201F50AFD000606FB03FF010E01F9F707FD000C04FD020F0600020AF403FE0707F902FA0CF8000503FFFA09041019FF0CFDF8F0F5FCEC0BF7FEF80610FF04000504F5F907F204ED0009F70002FF03010208FE08070404F606FE11F4020C0505FD05FDFF00FAFDFD03FDF10A0103FCF801FEF4FD0604FAFDF2020004FB0A021DF90308FB03030109F008FF01FF05090007F107FFF70607070600FBF602FC09FEEE00F7ECF201FCFB00FE0000FA0803FAFCF8FB03FAF8FAFC0B0903FF0906041003FBF901F900FE05FCFEF5FB020203FB00FFFB050B0503050003FEF8FB050105F9FE00F9070802FC0BF6FC0FEF08F406FE0313070404FFF8FBF9FA090AFBF9FD01F4020504FE020FF8F3FA00080308000C0FF6FD010BE603F305FBFFFB0111FCF5FC09F5FC0D111200FD0306FF1101030608FF04FF07FFF6F40301F303FEFD0903FE13F201FF03FFF5F703FD0A10FAFB0105FDFD0D040FFEFEF8F60CFA10F2FE00F404F7000DF304FCFCF40F0209FB020300F7F1FDFEF5FCF8F9F7FE0DFA04F20D02FF0B0EF80AFB0AF90A01FBF303FDF2FAF706F802F806F60209FB010702F5F502F9FAFCFCFEF9FCFDFAF80108FDF609FB00F6F1F7F607FB0C0101FB0BF00BFFF71A0104F004FEF3FD01F90DFB04F507010B09FDFBFC18FFFAFC040EFE09040004060708F8FDFFFD01050703FDEDFA08F5F4F0F2090811030701140AF9FC03050306FF0A0D0109F5F1FF0107FC0200030DF801F5ED0613FFFEEE0C0A030705FB0A0E00F8F5FC0A0C02FCFEF9070F0208030E01EFFB08F5090A05F8FEF7FDFD1001FEFB0209F70F0202FA02F900FCFE0208FCFFF4FAEBF7F6FE010801F606EB04FC0BFC04FC00F8F805F10FFA050DF30407FD04F0FBFA01F602F8FD04FD060EFC080F070F0014FB050F0106FEF00503F4F5FEFAF600F0FDFA0FF604FA0F09020010EFF5FEF904FC090904FE050900FFFA05FE0BFB00F9FAF4FDF4FEFA0CFC0D040701060204080E06040402FAFB00000006FE04F0FB1200FCFDFBFE020C0A0BFA010C0CF2FB030404F90D0208FD0900F7F9FC030CF3EE0416FE08FF0DFEF0F3F4070DF904F907EF07FFFA04060406F0000D00F3070406F6F5F808F4F8FB0B05F90EFD0705FEF0F9FB0803090C02FBFEF6FCF1F3F9020405FD07F30EDE000108FB06FCFEFEFDF3FDFBF4FC0302010204F2FBF50309FFFCFFF500FE0C0A0A0E05E9FEF90609F80405EEF8FD020303FE0AFE16FEFFFDFEF6FB07ECF308FFFF07FB0A03F6FD0004080FF902FEFEF70A09030A0910F80E000E11060DF505FDFF0402090C0FF708F6F2FDF505F5010706F90606040704FDFAFF00FBF7FAFF0D0A0110010201FAFAFB1706FE0203FD03F6F5FCF8F20D021205FD050BF6F1F10205040BFEF3FCFBFEF9FEF1FD0500F9F9000106EF08000908F4F8F0F4F8F90602FF0601F3ECFD04F6FE0C010202F4030001F900080704FB030108F9FDFEF8030602FE04070BF8F50713F8FAF102FB0DE6FC07FB04F707FC0E01EFF80B00FC05FAFEFDFCFDF703FF0706F8EE0308FAFDF501FF02FCFDFFF2F5020B0B10010D000A0A01FEFDFD02F60205FAFFF109FA0804FB040F02050012F603FEFC06000FFF01F8000A0613FD0BFAF6FDF806ECFD09F20000F2F8FFFEFD14FFFCFDFE16050C03FC030D0811FD16FE12FFFF140CFEF2FA0806F205FD08EBF2020905F9F2F9090601060300FFF9FA14FA0B05F4FEFD08FE0D0A0003FE07F7FBF8F7FCF502F70204110C1101F601FDF1080D02FF06000403FAF90701040AF0FB0103F5F903F4F0F510F5FEEF0A0107F505FF0401FCFD05FD030B060FF9F602FCF8EBFE06010DF70DFDFA06FDFDFDFA04FD00FB08FAFF090108FB05F00C0BF700FB04FD05F6FEF2F80608FC03FEF7F7020B050FF60EEF0600F7F50A0C0305F7F6FBF3FD06FB06FAFF0012FB0602FF0705F700FB06FF09FD040206F202F401FF07030105F8F305000E04FFF7F8F804FA04F70006FA0BFEFBF8FDFFEF08FBFE02ED03020BF506F7FC03070400020E0608FE04E9FB0106FCFEECF902FE041700F6F605F6FF05FE00030404000D0500F8050200FBEF0AFF01FEFCF40604FEFD0A080008EFFAF7FC0607F9FE0106060108F4F7F3F004FAEDF7FD06F8EC03FD030E031108090A090301FEF60603FDF501060A06F9F9FBFDEE0D0805040902F403FBF901F8FA0602F602FAFEFC02050C0902030A030D0D08F30609F80CF70507FBF6FD08FEF7FEF909F6F90808F8FEF904000BF809FC01FC0C0DFB0AF20404F912FFF501F60A05F2030B0614FF030000FC0008F60C05F200FA070104FE0FFB090C0E0806F0FEFEF8010000FA0305090507F6FFFAFD0D020BF80511FB01160A03FA04F309F9FB0D0D0302FFFAFE05FEF901FD09F51201FF0F05130503F8F7FD02FAFAF903070D0BFD0D0C0605EB07060909FE0D0CF500FCFEFD1306010502FBF4FCFA100DF0FEF9030009020009040901F607F8FBFFF70CFD04020708FEF506FCFEFEFAF702FDF705030902F9FA01FF0110FCFB0702F9FA0BF70C0A02020B0705FB0000FB11FFF2F80BFEFA14F7F6030108080001F111030715FDED0A0B05FAF5F7FBF605FCFD0401F3FEE7F20007060E020503090608060306FA0DF4F202F4F2FD05010B0005F7F7FE05FBF50BFFFAF80707F706EDF8FDFEFAF3FF010905FE0B000909FB0AFFF801FA0D0C05F7E1090200030B00090BFA06FAFBFA0A03051201FB0600F4010F03F80B0AF1FCFFFEF101F704F4F201030214FBE0FCFFFE0CFEF2F5FB0B02FBF8FD02F5F2FA0405FEF60BF705FD00F909FEFFF301000902F510F000010001FBFBFDF0FFFD0E05FFFAF1F91304FE04FF0201FD0A06090305FA03F0FE01F6F802FBFFFFFB01F4FB0AEB080308F9F201FFF403FC0C0AFDEF0F010203EDF3FF09030606FFFCFE040101FB02FEF3FD06F6EF02F6FDFFFD0100FB12FDFEFFF604020F07050B0F1AFD0406F7FAFEF2F70F0A090BF108FFFA0402F2FEF7FDF9F5F500F9F5FF0F06F50A0204FBF6F70A06F707FD06FA03080306FDFB0905FEFA08F00B0FFF11010403040600FFFA02070202F30A03FBF1040C0F0008FBF904F70E02060A08FDFEF5F3F6FBF80807090AF6FBF7F601FCF41700F806FA0201FBFC0B0BFBFC08F1F401FAFE0D0406000407FA090505090BFD0B0507F4FD0A09F90408090AF8FCF40CFA0B0218FBFD0411FEFE1004FDF201F80213000509FF110CFA01FA0201F602060811F30900FEFBFFFCF10106080A081107F1FF0C0303F506F6F703030AFCFBFA0EFD02F4020102E805F90501030FF4FA0409020109F2080BFE01F91302FCF61803000CF4050BFBF5FA11FE03FC031402F8F0F802F9F6FA0D1508060605070AF406FDFBFA000C0A04F90003FBFA0E0007FF0000060600FCFF01FD0009FDFFFDFFF50D00FCF5F5F3061308F1FEFD060903FF03FCF7F2FEFF06010904F214F703FE0104000B0405FA06FBFDFE03F50202110EFEEF041205EA070607FB00F9FE03F8F7FCF60F0512FCF70200FDF804FD0702FF030804160803FD0C0906FE03F7FF070E0219F5FAFF05F20401F904FDF2FA050C00FDF102F4F60BF90CF307F9F6FCF5010BFA0601FFEE07FBFEFE15FEFA0CFFF5010DFE06F60700F6FB05F708ED1201FCEB0AFD01ECF7FDF5FC010300FE02FDFEF7F50803F4FC05110400E90211020901F705F606F802FC09FF0B0AF9F9F7FEFFFB0CFFFDF309FCFD03FD0F0FFD09FB02010CF30001FD0103FEF1FF08F8FDF3100AF908EA07070201FD050C03FA0D1104FBF7FB08F20EF9FF01FCFF080313F70001F8FD0B05FB11F7FD0109FE000DFCF603F8F80A02F905FFF80CF9050607F80204FE100000FF020B04FDFB0109FB1001F7FFF8EBF4F408FD0F0BF9030EFCF4FCF600FE030807FD04F8F3F8FB0A08EE050304020606FF03F80AF006F1FFF806170305FCF507F704F80A10F7FC01F900F602FCF8FB0009F10704FA0404FE0FF70400F000FFF701FAFD09030203070901FCECF206090109000B0FFE0A040203F7FA0BFBFF02F800F9F8F9FCFC0C0504F9010FF9FF08030504FE050104F8F813FBFE0B00FFFA050603100505070003FF0D020FFE01FB05EC12FDFEF009F4F4050BF6FDF20802FAFC020C02ECFE010602FE01FBEFFFFE06FC100EFEF6F6070AF9FC05FB05F701040909FD03F1FAF400FB07F908000A03EF01031005F511F9FEFBFC06050812EBFCF90103060516F8FBFAFEF9FD020AFE0A0C01FEFEFF05F40303EB07FD0106F9F601051206FE0E00FD03FD0E050C05F3FCF400F110F804FEFC0902080807F80CFAEF0A0F01EFF40D0305FBFF02020B040000F50706FD04FBFCFDFAFEF604FEF905FB0B08F6000C0E0700FFFB040209FD04F5F202030309FF02030D0A060B0404F0140201F7FEFE06F3030F06FD06F2FC04EFF6FDFA060407FCF30508070703EB03F9F70A010C0C020EFDF8FEFEFD0B0C0BF0FFF90714FB01F90FF60AFEFAF407FC0BEF1205F001FF0508FBF0F5FCFFFD04FBF9FE00FA0A090E0CFAF7F3FF0200020905F8FF050CFFFCF8FBFE110200FB060AEEFE15FD0BFB09070FFEFC12EEF80D06FD0108FB0805FA02FA110A0607FFECFA00F904F3FF06FB0C08050A000AF212FFFEF5F50204120305F803F5010508FCF80202F90F02F81705000007FB07FB120300FF07F7FA01F9FD0A0212F8FAFCF7FBF40705FB0B070204FAFFFEFC04020D08F5F70C04100FF9FCFDFBF7FFF3000A0800ED02000A0304FFFB00F7F80A0706070115FFFA01FAFAFBFD01F8FBFD02020204010AFDF703FDF2F10801FBF8F9FCFDF7FEFB12FC06FD0AEBF6F600FA0BFBFE06FFFEF9F9F3FD0701F3FF080CED10F500FC01F4090E050407010903040807060404F8F5FB05FFF1090CF0080BFDF30903F9F804FBEC00F60201F50302F6FF07060807F4EC05F8FE060DF8E208F300FDFCFDFEEB08F4090104F50106040E0B0D031608FDFF0E09F80DFE05F603FD10F705F9FB0609FCFA02030C03F806F806060506030807FE0107060005FA0EF81108F6EF0C06FC04F707FC04F202F7F60B0307F3030306080EFF0B0BF8F9FF0106FF07060104FE0A00F00204F9FE100708FCFCFDF002F4FAF6FE02FF07F30B08FE0E0CFAFA09FEFE0508100CFB0C030F0306FDF9010FFA03F6FDFA08F605FA08FFFDF407FCFF0600F416FB07FA0206FAFA03FA0507F3070E03100BFA01F602000C090004FCF6F30FFAF2FAF804F8FD00FE06060601030307060002000105FD04F608FF0101F90AF6100900FA0500F601F70909FDF1070C09FC02F4FC0805090DF10BF9010F05FBFB130DF20106F104F202FF06FCF1130CF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BCF62CAA4F63293C7A8F894856E9F263EB9CA4A0648141B4B0EA3A2D3364C36A83");
-        int smlen = 6209;
-        byte[] sm = Hex.decode("3FB2759E1BCA07730566D4FBBF3FC19A39C68F190095789EC555884EE97E9ACDB09109C5A9CA96658603D63A5A23C9D8DB7D8C54D07738E958BDA1E631ADED4329AE16B183374E1C3976DF1EC94BC79B773E8569973EDA418AC903B534A83B8B2F7CF7856ECED63F2626530EBBE3D22A734B457F2B1F168225753A0F3B030251CE7F414740D707D10A65BA6F7464E0291749AB9CC694A81CF54AB8AA21F3687D197FD3AD1EB37EAB1A19A67B1C396BC480B2CA19D650FCD9335FF245863787275540D7D1F5FCB278C558AD6CF15C154073A846B51754D45A2439FC08382E93CB657E1CFD3B3DEE5394C5F81513041A356D2EB938CF196B1B4BF96186ED75697745A3304BFA7ED23F77DFEBEC60A2C2441EE6B695CD17E2166956CACA25B09DE505187CBA23F62D86D0871BD6B8FD2D116743110DA3386A7401DAEF39506C9091B5D32675660A7380C5E837FB7206604C1A4931B8D6D452C5F4095EC1B7EF0CD11710DCB6A18749B788079AB1902135B62280D928167559696A3A2EAEFADE345F91D8CE49EBB109CC926C28077AB5B1B9BA946D929C2BB969061829A6E58A0FA5B168F5A1A7722CDF9A23CD75946CFAD9D624F4DB6A47C78EDA05EDBFE164F6FCB4C32BED70FC7D04BEAD5392F26ACF330B99A9A4140B5ACD0F215A16CC463F6BC4B7BD5D81B51F98C586E58C68BE97ADA48CED599933F2CDD994B9B52B3D93C44D9ACA191FDC0FD894C8AFD58B05D73432D6A90991FA87089B68D7CD7D1B136F7E3BF0FD266F82D13B1E456FBB6E29DF3D330E2D9440AC0AB5805C524463EDCA6D27ACB13B1B4F0881FF3A5A19E2D512A84192FDEA959FB87A555610DD44497EF3AAE91D075EECF7945FC2B8FC531B53FCD236687D05457999E1B3510D92FA433205E4E42E5B474E2FE7EAFD88BCD3375DAEDA0FAE4D40A63E09F2C99CD9620279BD1C34A8AD5B014CE8898B17AB33C1BD311CBF3CA6EBE0C9217AE3D09D978C0A8A41FC558A8D31BF37FEF59D1F46575CCEEA07F500E1CD85CD00FC20B449892E9C463C60342F526CA6F25F6291DC72330B5DA6C24CBC0CC75D8146CCFBD1FA47D36250EE5113BD9A54846E40FCDE4CF33E38328B48BCA3953C99495AB14B5AE2ADAE893181B1AD49EFF63D3A432DAA67572006A4001179363EAAA1701FF939D69FAA27EBD7BF71DA1029C84B4980251ED9C19AE67591AC719D2038D39BBF95B29B217A95CE10DDADF65CCD90A6BE492CFE0345D5AB3BA26F44846E96E330E853D68132B6F5B1B8913C02997551E55EF4A2C48B0EED24060FD549087E85ABDFADFB35B13D9A44EC677818EC64CF1446EB9F012160E5998272993377D8B751F87F8F370D904642EE9AEAEECF498ABEFD14FDFF1DB90C41401FC4F6DF93429A944ED1F90D834D7C5FBE5651FE291488605872926ED853C0B23E958DF376C75AE1909BCE043EA6AC3B035ED0350C07DE7749F7181E2DB5C5A4CFF983634AC34A4D416CBECAB4BC40C96EE2AB20A513E865B792F2BFCCDE4F9107CB9887F3FA77F4FA9D625EB32FB856036768F6E89424345BEC0CE938D71C37DE7C0CC130957C15D06E764D819AEB5E05328C50BBF7B01EA9F2952D5861705FE66A8EF3F22295C408259901ADF5E675A9D72125467B18C75A0CA5C84BE77E8A678FCB1A98EFD90496527B509CEE1DF01B1C535273B109E5ED1F8C6012EB79C7F5F5B9412A4F7DE6FFDE45F10F79BF1135A370C953BEF71EECBA75718A56441049B8C306E371B02250B179ADC42B9DC0963C9D6F991F66AFAF05B7ADFE8FFA71E8FF3BF14B08550A8499F543A8FA45C4BEF6D4635EAF889652E4EC4E24B7343C48F32C61C661291747B6FF99057C797B7FE93A58FC219D44C3973E1C11973F8CC00A29EB43235E0B9DE326F134FA55CFEF1B7D1AA00FFFCD8A3F9AFA1127EDD97C5D20D642203F65D5C5151AE773B3683ECE96726CC61C13997CC8C8F291D77779686E33BC09EEA6B8739122D2B90B78022F368B01833D94ECB2091BB45A45AA7784603502B1C54657F8A7F6E122C7B46393A466933BD806FB4411288DCABEA5E2788AB0C088AC9A45C587C087CA9FD4C131969BC9F2025DB8FA4484CBEF0C6C7D887FC22599C28593DC8D586ED81D38D35249D7E01FF6DB898CEE78B6214EF2C215B76E55FB0A65CA3EBBB5586F4255AA2BD802C5E2B60A6051B5C4BFDB48C4520290F2FB1BD5A6D156CA375C1E700765F9923ACA126D41CDBA2F2DF339D143870CDC2964B20483154EB8E8D59651C17FA7898176073DD353B629D327BA130F2E56A9594D177A1EE82847BA8F8AFDC669F7BE160F1EA6C6EFB747D9BAB0AC58DCDC8D33B5F7BAABE5274DD7D4B5AA439C713615D4B255814A5D2AF44D5382264F3F9F48872A38D36D1F15C90FB768C9B690EC989FF28CC135D29330D84EA33EB173271DDF8F5DF885076251370F59AC0E4DEEF640D5C04ECE8778C1D8F839462C5504916CBF555167836076C88A848903B3779E007745F3CCC8282CC526306DD63492F0A004A99821D1BBC065A255F4FDCF426BF97CF9933DA6A79B577919F7605446A25CB549A3057785522EF5A56E3288D42D714163F16A36EBC6607779691C7A1E5C420E24A64299AA03930DE872575A18A0D004CE7CCBE85BE4227F2146E176F0B0689BF51988ACAC366444E2281B5A81FA505E01223818185BE05074AF0C94C4B99F1AE6CB4033487DB1EA46B3A59A1B840845570F468F6E8BE1CEC813148FC421A4A62BEC0D8CE9DBE73AC17D44FE36C93DDCAB402EDB129BAA93795AE54608169F58BD8D115EC6392DBC2BEF8BBFB820827E76B266F480D4E7229B4119C35AE0AE1F222DAD22DD9CB3DBC76D80722F74E6BA87C7918BF33811850F3C9300E0FC880B889CD3EF2C3E2D63B21AB7A05867B460D7B96050BA8F812B79B29851CE5EA92202BB553972EB1ED8CB82289987EF0B314D2E787DDE3CAD96238D7BD182CC404F8B19F3FD4AA55C75DFEAFBF6228A37B72CF57152B4CF7A65872ECAACA21FAC42849BD2D1A5812196AC6A52EB51709710AE1BE2345465DD98D42135BDF67C1A022A338F4E60686746B62B2624FCA52A46DBE515714A2C8E9CF76B14D2A7FA3A734D27657DE99C8A0A5883A1A9ACD6568A0F5926E93AF2017CCF8623C7363CB52390A93FD1143C575AD3B35EEC387321EC87B93C16FF30DC8848DAD229B0CC561F75F64D948A0CD82AC62DDE0AB9DD13973E9B6A19DEC429620A6BD52F0203820F3D2DB2369D121A093E64071E926919BB8B7BB0C3E58EDD9A69F5ED8A1C7210290BFADCE31284BB69C5F02B5E8D13F19E159FF334DFC5AC94798E1C87230424AD32FAC495299F357F74D914FBA9349F0D26491444C5A7B2F0388ED098A182122BA1C1A8FB751C52376E4385449B06F92C3005EF39BDB758AB542F7B3BA43A9D6D3B1B88977712E0A8F08C898AF5DEA1DD4E83BFB763DCDDFD34EF33E208F98AF8F90AC173ADDAC9E701B3F014560A1D029598D91C6A5715A4102F2162B8FA44A4B9F002E0E1C8D2C5906745708648A690B8CA89E6ECDF1BA155EF1D256305D94D790A856C79E39A22470824AD09A52F96D15CA3B0C09D19CE23C7880D774AC00D59C3009183163FC98FF74F99E03A550283A8D300E1C618678E2F8EC2D69B15E822571ED7A5F35D6FCBE5AEEDB56FFB6352D60434CCA2C5DBA90E82AD9298FB1369A06A1FE93098C86AAEDBF846F59A3390845B8EDD2FB8D08622AF52DFF43F1918EFCE00D5184BC19D4C782027248DCD875EC1B9BDBFDC7DE08A92E4838AE7871B897D9F5BD23256B9686CB001B5B2DBE7D79B2094E2B26C103C38E81F51A75DB8871A6AD28AB5AADF94BECF43D928B155FD4900A3F0F2332B992C0091B8B21D8609BFE9A1EA84313ACB83ADBC8478320BEBEDA9AEF0BC1A9B3FC4BCEEC226D13AE4A5163DEA4D019B148EF98285717AC45A935CB71B103FFFD9B6739CF1916AF5F3D61AC37CD9584DB4A2F9A24D38F61B83232DD33680DC30C32CFB9DBA27A6BA2BB8B2159F3EDA7F37402212755225CD99CC1A37A99F2413A4C57D1A73D9BA1DD02E310DAD355A14FD01A3EED8F73008607775AF0A5BE621EC16F2111A5148F977E9F79460450CC21AC5FCD8EB04BBA536385563D288BF592D3ED7BFA3A12CD9F914CD0C35770A4A928705D25E13AB4A05C437E009595B14381A5E215870EB468B06607F30FD88CACD1C11478BAB4372B9438AE4DB6C606941BCE98322E0FDCF59009DFC5E7A4C483B00DDF2386A236272DBA0D8064F27B7C1BE90DA8B592CEA17EB33ABD5A7E19DD69A805A6AEB6773D0E8CF6450C3C2FE55E0B7180CDD8863106D75431482BDF9C182977B91330B0F0ABF796D5348CD84FE0036E52860F7801ECD8EA9E69784994D425CD5ED09A63251835A233ECA4CA9B9AF6CF154AE4465EBF089F19D32024AA2C144DA004F84A7A6F22C3D71600245AFF5F1E5A1D9D5CF24FCFC0228A534E9E6447CE7DDEBD8DCCAB3BC0933DB333A39031EEA2EF6B81A9BB34A7F67554FDFCA8048FCD97E76CC082A93FC4F98D32910B895682D1FB3F5E97E463131F56364283ECF2AE029149B9CB73817AEECA497360C23ADCE8111D86E2C7005ADD2C8C0503A99DC91D30F9D20866C17E296649B161416C181F136608A05434074D567CDE728F7B31B60B97C04055698E108E6A2A65A77EDBFBB2F05AEABABF166B6DF25DC3A70062050C779CBBA550A62F021CD6222165082EC37858901D0A46C815579AA998EEBF4B3387B6BD71B0F16EF5B68CBC6A47E1A3DE015331953F3D3510385F1E048ECB84E7EF929142225DF16234EBD672B686D297E5A9168C6F97EF6C97501D4E086E9D73D1994D3C38C912691D8F0E00E0E2F0CD95B42F3F70194E3183BF9537188D1105D44839F7F3EDAB0E10232DAA9FC673D6071F3589095B6C392A766F0DC9D9B8DD563B1EB82076CBBB7C5636C516D3F200A3B43D384E79042E936E51C3D1A4D3F621CF738EE51D6D29F22D658E2370410E841A5CA7C3122AC26BF26730AEC0B23D8CDB46B6F3E0C4EF15410B15D83BCDF67BF2AD9EDBBC3890F70670FBFF86A4645A58F1EB5C49C8F6973CFEE2E1E38879F59D1740172A52744BCD839CACAC94BC3DBDA37A97A2FBDAD6AE0CCDF080B57CDE454092D763A1020E176515075DCC37148AEDAC1C7BA090B12DBA260F08F04C689661EB926716ADF5FB7159822E226BD3DE2EBEE1F98685CBD99CCFB09A6C9FBFBE377A122FDC01B9B7FD82F759129A6D0B0FCC9E8AD566A19FA04C12B95C4154E35215F5F11255493AFD5E66B73D4E0572F01454D1848A13BC0A9374BDC90994B0004D9D2EAC8FB506E7CD764A1B2A9016BEDDBFB987741FB68265E5B05CDFA46A4A7A1BC25ABFEF37118CE8118BCF92F32C433E5252D8DFC195D4CE5699A2CDC1612EAA171423E6299F86C2A976DB4C1E421E140BFEE8406DC649C57FE27A77CAF3481B8687DB07F9921790AB5D502CED457C3538E688F143A69175A535101E1DEE82C5E39A579DA178CE233105BA5275E27E79441548D9E0F70CBEC5C7E179D6FD8E167F90C3A79D964CC23CFE18D6BC300753961BAEF0EC6AEDDDCFB1760B6DB3D0B667B8E120CD3F867017507783F223106CA7753D94BC60E65538589FA87807F7AB8791C1957C9510A9FBB91D513DE0ED7166155E911A5622A6A49DCF1DE3DEA4072E5DA75583B6EFE781FBFE31F7AE50F4FB8897586B8078144CAE9842CEDA34EB9E0B996E3CA46F11BCCA46ECB81DFB191236046294BB0120ADFDA2EC0DBB7D4CC6353BE9E64FD668710B1CCD305349C0B7877566E3DDC4E21985FA6000229004DFEA9F39421476A5E33C9163CDAE4C7676B1097E48C70DCBD4B39CE4AAC3ACFE73CE9C0D826B8A61CCA0DD2340C860E57496A1915BF065AA6557E26A151603D31CA94410E6C3E6D5AC80FF6F71DAE371D020CEBFB58EC73017C8D2D6D6221598678D752DBBA95113CDF71848D2E05D22613451627456A96D15BD277EE04884F3F296B0DC5314EBA22356CE09D4C0BB7246419D8674E4D10EF64B3F057D95E70BF1086ADDB100453D81E19939BB18AAC1343FABE39E308284A8D6A38BE238B6E0503A504727AC20B48A2C26C130E7A9678BEF07A1828ADDAB90740526F99C11D3217E001172CDD33D6D6E949A8ECB963C0A5283189AE8C98B21933FF691931758E842977A4F3F337252105CF22AD836F91E4FE5CE97FE4CE59EBCC4DA2A56D9413BD356BF65EF6FB2CA2201EB31BA56D717D2F9C038BFA76A52610AEA57D45500A1408AA8876AFF585A2FF309C3EC852B9568AE1CF9D97635759932E61D61DD6E67402912EC538BD0DF9D0BF25C862C91E301AC499E64C061FC14E89EC361A10FA0C85132BCEE7AF028C663BE3B0E12EAC52F691FB66E58E34EF2AF4C5A9DB0A0AB01DA372E39BB824F9D6AF847DF6A08F680B4F99466D6E6F404F0B92210D641375ECBC0C10A38DD1CF449A75BF2420A294EECD649013E160D793A247D1649E03A535292ED3D88EF7901DC845CD9703BF0948DB61C08EA89C33A3DA436874AD22ABEC43A5F2896833E15B3102A66E2A7D69D272FFFD4CC8979B9733718B95B605166DBE16ED1FD1C5CB2571E7D9FEAB2BAF693448C1E9613E8FBB25E05E2838390F3D9D2CA88D4B1739AEF4039A168E908D245ED38CA96F98569FFEA949946B76BF6F1DCA0B866CF0AE157314A28B76654EFECBABBA1C58ACB48E61587B42DE0F5BF8D5A3DB6C2E1D1632894D940CF10F71390B1DE07A4DEC38EA93A67439361E71FD8149D26DD8A21E9B98DA1FCC014052D0FF54DA3B2A335D0122FAA1332B193043A9119BAB7BC8F3209812B5280625C44ADBCB92907B6C387E0BDB1B9EDC0377C03E305C7333897588C2EFDDDC82E95ACAA329839EAE514F8E8A46A9126847933E73820922CF62528DB950EAF940F72A7383A486F5E210120987173A75A7D1B12097287F9146735A7BA110E22BE8B2FE720B2DA6CD8D59C8A32E5B43BCB9B42B78103BA2C25C13693AD4F2CF49A93D4B543AD7E4BE7E7304074E906D114E77C9448A8318E71F67C3C5A3B3379B40D44D9D1740C8D5003B03AA614099CB77B97A1B121DE9F75A69CCE3B7F1A2AF7B2B7702919F45544F778D5DFB9B7DB1C5AD98B36ECF231300178EF1154F4B93CE197815F6C5D2C3A3EC2A3E6F9FCC360B47D5A362BF4A41982ADE9C1377EE1162A9ADA02B966F502931045537D5CFA700C5EE0B818C8B280CDC07B1C34046A908D31CAE7681AB7A59AC371326211C170E9240B4B00222F628ED734ED39D5D0A4D1D4937202F82A531B94084FFB7033595460B41C5DC021D955C8EBDD8787140F60FFA21BFE680234BF5EE2F3F998FD9662A657BABDF4FE4A3191D7456B928BA2C0F08596015B803CE938522A88B44292223DA54BB1A0318ED439C5E0B1513AF667D1AEB8A0228AACDADDB02F689CD43FD15B8B10B12359EB83936F9191BD4671A3EF7F5998B70EBE29E551CBC4EEB214DD776AB62B20D1DB533CB2621AC0D00D88BE1E21243DCEAA1494EBD581992F6F9171B9FEBE919DA5B61A99D2D5C3A23EB447EAD4857F217CE80CC5765E014B851CDB56518160B69C56F4A1FB9542792390FF4DF407E1BE94408A2C856DAF3F95EF4E1D3CDFCD5ADC8F9BFC95F5D7A9AF8835DF3205FD81BCBB06DC10A72E57855DF16794F3B0F0EC83B36922267755777517875390D7E5520D46610B5008ECE82C08E20888C4785A1CADA07E929E1324EF710D48212B739EB5276A4C895C46BE8A405332DF93E93BED6C2075261F90DB4ABA03B4F7884B663268AE18C11479098611FCB7B0F9645E315819995DF3F3A45221A6DB71874A0585A197A01E67CBC85DB02A4B3ED2BEEE43031E7065BEB838C601BF2B161633BC9C04B506564E5DF517F1445140029665EFB724F5F5D88FF654AB18A6D65AB04E3F36720A26E4D9676148020D634FF0B74DCC828394F9AFCE7C8443097A0091FE8255F03D5A9B5AA00FE9E6056B11BB772AA6361C51938447B57F3195081483AA1DF6B5A71D050AAD3C855A770A1636019DB9EE75875D542848A285140B1B7FE054B4FFC719CAAA3B21FC5BE4B92F45D7A99254733303DDDA18AB271CCB2DACA27B600CB5CA50E857070CDB657FE559B21CFB3C596B24727501707B64A657E3B46FF1DA18842E0227C02261F88EBA6F9502856752D5D5B6208FE660645860E42A32786C3E54E8AF6DCD3E9ADB76BC0200A4A17B1CD89815301AC45EA3070EA99FEFA310B7FDBCFBE817EF28A04F44A76B38015A27985E5D9D706997C1DF67CCAC869D4FC9972AF47FE2908C3B1349728796903DA78F67562D693418345E041FC6F9E3A991299EC387C53CA9DDC0428001216637F92AA19BED77CAEBBC2BD3972AF5B07FADF92F54E82210ADFD3623939BDEB5D5BDB019D89A1EBDDD98C1B1D6BDD854EBB594516183B32C24F4C8DF07943CE77857AA0F10D27D3FF0DEDE7026F924E34153F9741CB2D7394401B20BCC53965ABDD26881FD3CF2AC83825E1DC1D1402F679BA8AC57737EDF7FE7860580424559DFD9042C88DFDE010943F3C3BFF61283143C46BA1E85CDE3284AE526CD5DEA7A401F07AF0A88586C599FF640B4B9C4C601E650D7E9E4092A940F9E4771084112F8D8A7FA8E5DC43DF8214242AB81E8FB5A097F80E01C9B436C125DD616FF680DBB16A24F90F45A42CC1A05111E062348D14C1EFFAD81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
 
-        doTestKAT(QTESLASecurityCategory.PROVABLY_SECURE_III, pk, sk, seed, msg, sm);
+            String line = null;
+
+            while ((line = bin.readLine()) != null)
+            {
+                line = line.trim();
+                if (line.length() == 0 || line.startsWith("#"))
+                {
+                    continue;
+                }
+
+
+                //
+                // Vector parameter.
+                //
+                if (line.indexOf('=') >= 0)
+                {
+                    String[] kv = line.split("=");
+
+                    for (int t = 0; t < kv.length; t++)
+                    {
+                        kv[t] = kv[t].trim();
+                    }
+
+                    if (kv.length > 0)
+                    {
+                        if (!extractedParameters.isEmpty() && kv[0].equals(blockDelimField))
+                        {
+                            QTeslaKatVector vector = new QTeslaKatVector(extractedParameters);
+                            vectors.add(vector);
+
+                            if (duplicateTrap.contains(vector))
+                            {
+                                throw new Exception("Duplicate Vector encountered, set : " + vector.count + " in " + srcLabel);
+                            }
+
+                            duplicateTrap.add(vector);
+
+                            extractedParameters.clear();
+                        }
+
+                        if (kv.length > 1)
+                        {
+                            extractedParameters.put(kv[0], kv[1]);
+                        }
+                        else
+                        {
+                            extractedParameters.put(kv[0], null);
+                        }
+                    }
+                }
+            }
+
+            //
+            // Trailing block.
+            //
+            if (!extractedParameters.isEmpty())
+            {
+                vectors.add(new QTeslaKatVector(extractedParameters));
+            }
+
+            return vectors;
+        }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/RegressionTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/RegressionTest.java
index 2e51e7b..812c131 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/RegressionTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/RegressionTest.java
@@ -1,7 +1,7 @@
 package org.bouncycastle.pqc.crypto.test;
 
+import org.bouncycastle.util.test.SimpleTest;
 import org.bouncycastle.util.test.Test;
-import org.bouncycastle.util.test.TestResult;
 
 public class RegressionTest
 {
@@ -16,20 +16,8 @@
         new NewHopeTest()
     };
 
-    public static void main(
-        String[]    args)
+    public static void main(String[] args)
     {
-        for (int i = 0; i != tests.length; i++)
-        {
-            TestResult  result = tests[i].perform();
-            
-            if (result.getException() != null)
-            {
-                result.getException().printStackTrace();
-            }
-            
-            System.out.println(result);
-        }
+        SimpleTest.runTests(tests);
     }
 }
-
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java
index 1ec0337..b9fe45f 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPrivateKeyTest.java
@@ -5,6 +5,7 @@
 
 import junit.framework.TestCase;
 import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
 import org.bouncycastle.pqc.crypto.xmss.XMSS;
 import org.bouncycastle.pqc.crypto.xmss.XMSSMT;
 import org.bouncycastle.pqc.crypto.xmss.XMSSMTParameters;
@@ -33,7 +34,8 @@
 
         try
         {
-            new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(output, params).build();
+            new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(output).build();
+            fail("no exception");
         }
         catch (IllegalArgumentException e)
         {
@@ -46,15 +48,13 @@
 
         xmss2.generateKeys();
 
-        byte[] publicKey = xmss2.exportPublicKey();
-
         try
         {
-            xmss2.importState(output, publicKey);
+            PrivateKeyFactory.createKey(output);
         }
-        catch (IllegalArgumentException e)
+        catch (IOException e)
         {
-            assertTrue(e.getCause() instanceof IOException);
+            assertTrue(e instanceof IOException);
         }
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPublicKeyTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPublicKeyTest.java
index fa96e05..97bf97c 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPublicKeyTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTPublicKeyTest.java
@@ -12,11 +12,14 @@
 
 /**
  * Test cases for XMSSMTPublicKey class.
- * 
  */
-public class XMSSMTPublicKeyTest extends TestCase {
+public class XMSSMTPublicKeyTest
+	extends TestCase
+{
 
-	public void testPublicKeyParsingSHA256() throws IOException, ClassNotFoundException {
+	public void testPublicKeyParsingSHA256()
+		throws IOException, ClassNotFoundException
+	{
 		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest());
 		XMSSMT mt = new XMSSMT(params, new SecureRandom());
 		mt.generateKeys();
@@ -28,14 +31,18 @@
 		assertTrue(Arrays.areEqual(publicKey, mt.exportPublicKey()));
 	}
 
-	public void testConstructor() {
-		XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest());
+	public void testConstructor()
+	{
+		XMSSMTParameters params = new XMSSMTParameters(20, 4, new SHA256Digest());
 		XMSSMTPublicKeyParameters pk = new XMSSMTPublicKeyParameters.Builder(params).build();
 
 		byte[] pkByte = pk.toByteArray();
 		/* check everything is 0 */
-		for (int i = 0; i < pkByte.length; i++) {
+		for (int i = 5; i < pkByte.length; i++)
+		{
 			assertEquals(0x00, pkByte[i]);
 		}
+
+		assertEquals(68, pkByte.length);
 	}
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTTest.java
index 92e220f..0445a7a 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSMTTest.java
@@ -5,6 +5,7 @@
 import java.text.ParseException;
 
 import junit.framework.TestCase;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
 import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.digests.SHA512Digest;
@@ -27,44 +28,46 @@
 
     public void testGenKeyPairSHA256()
     {
-        XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA256Digest());
+        XMSSMTParameters params = new XMSSMTParameters(20, 4, new SHA256Digest());
         XMSSMT xmssMT = new XMSSMT(params, new NullPRNG());
         xmssMT.generateKeys();
         byte[] privateKey = xmssMT.exportPrivateKey();
         byte[] publicKey = xmssMT.exportPublicKey();
-        String expectedPrivateKey = "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "0000000000000000000000000000000000000000000000000000001f5bb70f454d7c7bda"
-            + "84d207c5a0d47211af7b489e839d2294cc8c9d5522a8ae";
-        String expectedPublicKey = "1f5bb70f454d7c7bda84d207c5a0d47211af7b489e839d2294cc8c9d5522a8ae00000000"
-            + "00000000000000000000000000000000000000000000000000000000";
+        String expectedPrivateKey = "00000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "000000000000000000000000000000000000000000ea874c176936e8456c1650167a7e900e54" +
+            "6f923382f9bc5a92a2df07d5080aee";
+        String expectedPublicKey = "00000002ea874c176936e8456c1650167a7e900e546f923382f9bc5a92a2df07d5080aee000000000" +
+            "0000000000000000000000000000000000000000000000000000000";
         byte[] strippedPrivateKey = XMSSUtil.extractBytesAtOffset(privateKey, 0, (Hex.decode(expectedPrivateKey).length));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPrivateKey), strippedPrivateKey));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPublicKey), publicKey));
+        assertEquals(NISTObjectIdentifiers.id_sha256, params.getTreeDigestOID());
     }
 
     public void testGenKeyPairSHA512()
     {
-        XMSSMTParameters params = new XMSSMTParameters(20, 10, new SHA512Digest());
+        XMSSMTParameters params = new XMSSMTParameters(20, 4, new SHA512Digest());
         XMSSMT xmssMT = new XMSSMT(params, new NullPRNG());
         xmssMT.generateKeys();
         byte[] privateKey = xmssMT.exportPrivateKey();
         byte[] publicKey = xmssMT.exportPublicKey();
-        String expectedPrivateKey = "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "000000000000000000000000000000e5a47fa691568971bdef45d4c9a7db69fe8a691df7"
-            + "f70a9341e33dba98a215fe9651933da16a3524124dc4c3f1baf35d6f03369ff3800346bb"
-            + "d8c2e179ae4abd";
-        String expectedPublicKey = "e5a47fa691568971bdef45d4c9a7db69fe8a691df7f70a9341e33dba98a215fe9651933d"
-            + "a16a3524124dc4c3f1baf35d6f03369ff3800346bbd8c2e179ae4abd0000000000000000"
-            + "000000000000000000000000000000000000000000000000000000000000000000000000"
-            + "0000000000000000000000000000000000000000";
+        String expectedPrivateKey = "0000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "00000000000000000007571b70ae5442f1e352d3e02cf0016ec96b23e51ff8e43c9ae0b75" +
+            "8d4fd37bb82a261908bac5a69ddb75c7d5b6d966d7210028b020667a104feaab7cb2ae0278";
+        String expectedPublicKey = "0000000a7571b70ae5442f1e352d3e02cf0016ec96b23e51ff8e43c9ae0b758d4fd37bb82a261908" +
+            "bac5a69ddb75c7d5b6d966d7210028b020667a104feaab7cb2ae027800000000000000000" +
+            "0000000000000000000000000000000000000000000000000000000000000000000000000" +
+            "00000000000000000000000000000000000000";
+
         byte[] strippedPrivateKey = XMSSUtil.extractBytesAtOffset(privateKey, 0, (Hex.decode(expectedPrivateKey).length));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPrivateKey), strippedPrivateKey));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPublicKey), publicKey));
+        assertEquals(NISTObjectIdentifiers.id_sha512, params.getTreeDigestOID());
     }
 
     public void testSignSHA256()
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSOidTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSOidTest.java
index 2353bf3..2be4930 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSOidTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSOidTest.java
@@ -31,40 +31,40 @@
     public void testXMSSOid()
     {
         DefaultXMSSOid xmssOid = DefaultXMSSOid.lookup("SHA-256", 32, 16, 67, 10);
-        assertEquals(0x01000001, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-256_W16_H10", xmssOid.toString());
+        assertEquals(0x00000001, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_10_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHA-256", 32, 16, 67, 16);
-        assertEquals(0x02000002, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-256_W16_H16", xmssOid.toString());
+        assertEquals(0x00000002, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_16_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHA-256", 32, 16, 67, 20);
-        assertEquals(0x03000003, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-256_W16_H20", xmssOid.toString());
+        assertEquals(0x00000003, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_20_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHA-512", 64, 16, 131, 10);
-        assertEquals(0x04000004, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-512_W16_H10", xmssOid.toString());
+        assertEquals(0x00000004, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_10_512", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHA-512", 64, 16, 131, 16);
-        assertEquals(0x05000005, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-512_W16_H16", xmssOid.toString());
+        assertEquals(0x00000005, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_16_512", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHA-512", 64, 16, 131, 20);
-        assertEquals(0x06000006, xmssOid.getOid());
-        assertEquals("XMSS_SHA2-512_W16_H20", xmssOid.toString());
+        assertEquals(0x00000006, xmssOid.getOid());
+        assertEquals("XMSS_SHA2_20_512", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE128", 32, 16, 67, 10);
-        assertEquals(0x07000007, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE128_W16_H10", xmssOid.toString());
+        assertEquals(0x00000007, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_10_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE128", 32, 16, 67, 16);
-        assertEquals(0x08000008, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE128_W16_H16", xmssOid.toString());
+        assertEquals(0x00000008, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_16_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE128", 32, 16, 67, 20);
-        assertEquals(0x09000009, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE128_W16_H20", xmssOid.toString());
+        assertEquals(0x00000009, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_20_256", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE256", 64, 16, 131, 10);
-        assertEquals(0x0a00000a, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE256_W16_H10", xmssOid.toString());
+        assertEquals(0x0000000a, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_10_512", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE256", 64, 16, 131, 16);
-        assertEquals(0x0b00000b, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE256_W16_H16", xmssOid.toString());
+        assertEquals(0x0000000b, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_16_512", xmssOid.toString());
         xmssOid = DefaultXMSSOid.lookup("SHAKE256", 64, 16, 131, 20);
-        assertEquals(0x0c00000c, xmssOid.getOid());
-        assertEquals("XMSS_SHAKE256_W16_H20", xmssOid.toString());
+        assertEquals(0x0000000c, xmssOid.getOid());
+        assertEquals("XMSS_SHAKE_20_512", xmssOid.toString());
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPrivateKeyTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPrivateKeyTest.java
index a121f5d..90adfc2 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPrivateKeyTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPrivateKeyTest.java
@@ -28,22 +28,22 @@
     }
 
     private void parsingTest(Digest digest)
-            throws ClassNotFoundException, IOException
-        {
-            XMSSParameters params = new XMSSParameters(10, digest);
-            byte[] root = generateRoot(digest);
-            XMSSPrivateKeyParameters privateKey = new XMSSPrivateKeyParameters.Builder(params).withRoot(root).build();
+        throws ClassNotFoundException, IOException
+    {
+        XMSSParameters params = new XMSSParameters(10, digest);
+        byte[] root = generateRoot(digest);
+        XMSSPrivateKeyParameters privateKey = new XMSSPrivateKeyParameters.Builder(params).withRoot(root).build();
 
-            byte[] export = privateKey.toByteArray();
+        byte[] export = privateKey.toByteArray();
 
-            XMSSPrivateKeyParameters privateKey2 = new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(export, params).build();
+        XMSSPrivateKeyParameters privateKey2 = new XMSSPrivateKeyParameters.Builder(params).withPrivateKey(export).build();
 
-            assertEquals(privateKey.getIndex(), privateKey2.getIndex());
-            assertEquals(true, Arrays.areEqual(privateKey.getSecretKeySeed(), privateKey2.getSecretKeySeed()));
-            assertEquals(true, Arrays.areEqual(privateKey.getSecretKeyPRF(), privateKey2.getSecretKeyPRF()));
-            assertEquals(true, Arrays.areEqual(privateKey.getPublicSeed(), privateKey2.getPublicSeed()));
-            assertEquals(true, Arrays.areEqual(privateKey.getRoot(), privateKey2.getRoot()));
-        }
+        assertEquals(privateKey.getIndex(), privateKey2.getIndex());
+        assertEquals(true, Arrays.areEqual(privateKey.getSecretKeySeed(), privateKey2.getSecretKeySeed()));
+        assertEquals(true, Arrays.areEqual(privateKey.getSecretKeyPRF(), privateKey2.getSecretKeyPRF()));
+        assertEquals(true, Arrays.areEqual(privateKey.getPublicSeed(), privateKey2.getPublicSeed()));
+        assertEquals(true, Arrays.areEqual(privateKey.getRoot(), privateKey2.getRoot()));
+    }
 
     private byte[] generateRoot(Digest digest)
     {
@@ -54,7 +54,7 @@
         {
             rv[i] = (byte)i;
         }
-        
+
         return rv;
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPublicKeyTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPublicKeyTest.java
index 9f835c8..f9ab38c 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPublicKeyTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSPublicKeyTest.java
@@ -57,7 +57,7 @@
 
 		byte[] pkByte = pk.toByteArray();
 		/* check everything is 0 */
-		for (int i = 0; i < pkByte.length; i++) {
+		for (int i = 5; i < pkByte.length; i++) {
 			assertEquals(0x00, pkByte[i]);
 		}
 	}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSTest.java
index 81afe81..3956482 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/test/XMSSTest.java
@@ -6,6 +6,7 @@
 import java.util.List;
 
 import junit.framework.TestCase;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.crypto.digests.SHA256Digest;
 import org.bouncycastle.crypto.digests.SHA512Digest;
 import org.bouncycastle.pqc.crypto.xmss.XMSS;
@@ -43,6 +44,8 @@
         byte[] signature = xmss.sign(new byte[1024]);
         XMSSSignature sig = new XMSSSignature.Builder(xmssParams).withSignature(signature).build();
 
+        assertEquals(NISTObjectIdentifiers.id_sha256, xmssParams.getTreeDigestOID());
+
         List<XMSSNode> authPath = sig.getAuthPath();
         for (int i = 0; i < authPath.size(); i++)
         {
@@ -51,29 +54,33 @@
     }
 
     public void testGenKeyPairSHA256()
+        throws Exception
     {
         XMSSParameters xmssParams = new XMSSParameters(10, new SHA256Digest());
         XMSS xmss = new XMSS(xmssParams, new NullPRNG());
         xmss.generateKeys();
-        byte[] privateKey = xmss.exportPrivateKey();
-        byte[] publicKey = xmss.exportPublicKey();
+        byte[] privateKey = xmss.exportPrivateKey().getEncoded();
+        byte[] publicKey = xmss.exportPublicKey().getEncoded();
         String expectedPrivateKey = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000073c3fc6de1195aa5d69f9dafc9db8504aa8059115e8319ca15cf58a1c83c0de3";
-        String expectedPublicKey = "73c3fc6de1195aa5d69f9dafc9db8504aa8059115e8319ca15cf58a1c83c0de30000000000000000000000000000000000000000000000000000000000000000";
+        String expectedPublicKey = "0000000173c3fc6de1195aa5d69f9dafc9db8504aa8059115e8319ca15cf58a1c83c0de30000000000000000000000000000000000000000000000000000000000000000";
         byte[] strippedPrivateKey = XMSSUtil.extractBytesAtOffset(privateKey, 0, (Hex.decode(expectedPrivateKey).length));
+  
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPrivateKey), strippedPrivateKey));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPublicKey), publicKey));
     }
 
     public void testGenKeyPairSHA512()
+        throws Exception
     {
         XMSSParameters xmssParams = new XMSSParameters(10, new SHA512Digest());
         XMSS xmss = new XMSS(xmssParams, new NullPRNG());
         xmss.generateKeys();
-        byte[] privateKey = xmss.exportPrivateKey();
-        byte[] publicKey = xmss.exportPublicKey();
+        byte[] privateKey = xmss.exportPrivateKey().getEncoded();
+        byte[] publicKey = xmss.exportPublicKey().getEncoded();
         String expectedPrivateKey = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f48df90f8e217076d8af6327691321bdcf63668c4bd28d021d49f2334eca845fa3073991049286c0eef5dc7f23ec0b31f5c1bd1e5b8edb2403ae02f292f6f30e";
-        String expectedPublicKey = "f48df90f8e217076d8af6327691321bdcf63668c4bd28d021d49f2334eca845fa3073991049286c0eef5dc7f23ec0b31f5c1bd1e5b8edb2403ae02f292f6f30e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
+        String expectedPublicKey = "00000004f48df90f8e217076d8af6327691321bdcf63668c4bd28d021d49f2334eca845fa3073991049286c0eef5dc7f23ec0b31f5c1bd1e5b8edb2403ae02f292f6f30e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
         byte[] strippedPrivateKey = XMSSUtil.extractBytesAtOffset(privateKey, 0, (Hex.decode(expectedPrivateKey).length));
+
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPrivateKey), strippedPrivateKey));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedPublicKey), publicKey));
     }
@@ -90,6 +97,7 @@
         String expectedSig1 = "000000006945a6f13aa83e598cb8d0abebb5cddbd87e576226517f9001c1d36bb320bf80c20270a1e779fd234d57b893ca841994e128a6df3f1832eb5998b16e3273084c84e049da83c891856b5fb20f17e1ecb6873c3a87e1a630e327b36981a5622aad6044c3f4d8296c4e744bca233a84b42f0524ee3ccf68eca45b66de7dfc123cac6951a57a802da0b6aa1a1e1ed698934dd2200331d1aeebbdfa0fe13f08ff8d3d2f5c9d542531e6021e34400eaf9d84911ebc0a2e84dff10dad8d078ebb356f522878fe61adbc7218d48af31516cc40689d06771b558c26a700da094d2bbd856c512c77c392b368f817e13fc10050b181aa5ef9294e6d90f1ade14f0774e70593fef221958f95f743c0b5db6a2b782b3b9ab10289fdd2b77d333d5476a0a3a48826e8992cc5a18cfff739deb931f5444b564c86d199916a947222c5d728922d912a30ec58a2422e0ddfe9cb018d99cb3b5df37f40911d2d86478280ec18f4f61edf22cd1eb6b785fab62ccad315afe6e1945c71716cfe4e9a549f591d258d2d128bd471de0054d1dae6523932f036d974dc80cd5eb123f5c4cf97b8747dcbf0ff2daaae1a2b928ad74a1441eac07053c9665650bd197afecee8271e54919695d83286d635afe6977b30a653b2792aa8d8ce6597c87e046c603f44355201981a03095230dd060c88b76fc48fe6714ba50cf8fd4fdfc537e66c2717d96e9f20a56c4d0858d1ca68bbebf405457ef5bb3cbf7af08063174e9867aaa3d7d65dffffecb2bf24ea82e05fc453e4c078395ad9996e3c2b0aae3311ea4159c1e59ebe3dca21a0adb6ebe7d495d96ade93ac1403804fc5d4b5c6d632fcbab7fe5b16394c60b076d2d02c85d0f7dcdc38a18c6599367e1711c6bacdee11ee16ee8caf9330809fc08961ea8f8bf7f1a77bc51f9a4b8b2bad1c55c947fd70986cfd6bbdcf6cf77241ba949b06f63ea54334b3bfeda451aedf7cbb15a2829cd1cdac3d20042c24faa5e3022634f43448d3fa88344b2f0d653940e5e0c01412d6a4508bbdb55aa08ce709d11d6b9e88fff643babbb4791e2dfd8854d66844ed3a2b58461bdaa5112781b92c03a8efaf344316b404c08795a144ee4a38914c88055bbdbeb3509b8cc7505eaca973d36dcb17ba354e9e0ec7511f88170c04a237610d4715179c6a316ef3500819237fe91135e71f90023e5e221b30e79f48e1ea84ccb1df84e52cc84ffae809a3b71176615fbb2c9e28be9f9b0833ba669710add8c972a0b7d2d9b7736d4aa35e2db99d70177015c4e6771f574d8432d25030bad4dc44ea6d4abac7be799361c3ae29ada40c0bbef4e1ecfd083bb52d7e91e17185febcf1e0ec0d566ed27c248427e50994b7590377762ae1bd7413fa4d73495682c45770c2bc01f56ad7a5522df28c46c1e683ddde8698353ff2a8ec29760350343e2fbb4477341113479d04be6346e783800516ced69e657338407125140ffe9c7179c3ea074e0df7d30f101e2f09bf2e6958542dd0d3644263aec86aa12beee467a8ec3aaae41fef986566a0991e867426bfdad50e5c8c8229fcd2bc85ef6f4a585a320663c673451b64f55aaba51bd160f857a64e428943e03e723873753da48b300a1987585ab1630729580a50502225c49897f208534e10e9def653f79eeeedcd599e4a3b3d5d9fbe3e172adb8643853add546c9b9eea6c49fce761242e858c2dced35311bf740c5bfe733ac2e536e531b4e9fd1767307d99fd4f1bc5b8675f598251f2a4c56ca580aa9cf90ad284097cc3dc150a7e821b81d55b5621ec952577e83482ef94ae393ec2b4d92575e9d7b9cec342cda8345a5d649d2e47884d7946860e066beec1a48936c98730ab6823cb2c7374281d735b7da5abc7dbfe6cfa459ab27cbc808bd464ad3fbce37d26698a68095d97506923991f9257ff2e56ed8cb90c0e15c15e9b817c8808a7c84753678b442f44668eac7ae1d8321eca933c5fd470b7587efce06146e34106b0994e1ee02c3541842cafcca19ae9a9ccda89d371fafe4f50047a8b9406ecea2990b90eb6da266724d8e6bccd6dd7ea88109adaaeae790790f0fdfd9b64364c37f9f1293fa60c8f5111bc21118932b0a7223fdb680aeabfedf717995a5f6a2986e647dd52efc6a0b733b57f4008f8acfbe7737b65a525ba04fe91a9fd654a36a8f963561db16e5715408c45ecb32f0e3f92cf22652625ad46edfd19e137fbdbd299cc53f8eaf88b58a13b736650a03afcddd920a34d42c745d690c61e7cefbc8ef496e639d61067c36a0c01caff42e606d9d2915b27e307efb59736934f1bd6e14f19767cca6da00b15a5cea1a6bca237050895a377338a10bb47df8dce188e510fcc1418dff371643d4441332280f81b7e42629e4d2b93c344b6a49df059adf75aff24adbf7efae31abc51ffa71cd9ad06f522c3609c797535782d9bb12fe2c15eadc64a99def3707b05705805b0c6ee9b5c1df4daf1fd42b70d4b9d2f740984d7b81ea164d0033838f3ae5395c17b56d55af6e599f720f0f61e9e5aba451744c6ae136091f5d2998759a0d609937b3e6639f82bbe9ad43e0db5700cbfe2e5697ddf34112d0b710ac4b5d2b90f4a4f48dad8fbbf8a983afc6b3ff638fb62e3d166de11aad553dad08d3c7a2bbc4a1bcddf96b3f319f7437710364d60a6ae3bd1d8a32faa0349093c5101da8e37a33222ecd1603e677bdcd28b8e4eb824673c198ec05434b339b865291e0eacb87ce7f78e3fa8debcf346cf16f347764e8591f21ba624c3f978b1d2a21d02e06b84853a1c94388600d208abc5ccc1728b14d4a18da3fcce1e0d4656105fea7ab7673f92d9094aea3aaa601969c4f64d9b5e3eb132094631864903fe80d6365960923b4e96212f85b78e0586cab0f1663dcf17beaf051071d6545190452d78252d8968b0d8cab0257bcf0ca1ffd3c3ce9895271c12e80d05c3345cf0c8064bcd8a760ef3e534c06c3b02b992f4bccc3fc7ff364a07183b43a60cb6666ee69547ed53895f6be2083b11d5017219c006469a8818787a38e813a3d45609c3d7ab0eef9180b127740e9bad05f93de0e81477f50e2b8719d1d9a3c1a55d5460711b88eb4f2c8df8d17d38832de9d4251142aa2cdb5a9d81a0c7a98e2c3c95bf96e73c68c78d467186eeac1dffce3853a6a544a1331bf650758d64bb8fab1104170824894fdd8a284d281aec9d62705683d8f8de11a5845b9d1b5cedb7fd0bd76da3341e75ddd05f261520228664d016b265f881e4b7bd303b7c38b8adcf31a7c211bcddb5260080105c1b70c41ee99d421eefaf1e51d762f245853cc36250bfbb3f547770303dc8a38d03dec2d0a7233b634f83a2456f279126c7fcb47f9301cb2d5e8db69f4721f68000d78cffb39317371748b85e824bd82284875b7bb65604c94ff0b8816cef677ade31963bd514ba59e3d998735a963d018b840a80be11a52ccdadaceb7ed45f0763e6cd49ca6af97f6bde85e4569178192bab4fd59d8c2b1fec181f27a8fa2c17552c16e3b5";
         String expectedSig2 = "00000001994b105a9224e5edcbc265ef0a76939005cecbe87097134c0b52f1d1a41270c21a7f15d081b126ab18592db4cabc163790011d2fadfa7483bdf88059bb8eaa6ba998998f7d69207bd41bfe452ed03ee118aeab3430c65105ef59c113ee601c9ab166a28a181c18530915e6de51a3f63a35a2ae39ce610875bc32e6b06c7851a4556bf2d375c8a0e7498a436cea199563818b4a42cf9f2bd630354c52d6f7a2d70e4afa512104168ab91798fd7f2479bdc6d946a59c7c4d512f4c221f79c8aee49bb0ffca4ed14934fbdf189888c9d6f9b53fe29727019cfd06da3ee308870f2e57f09b68e253ac19e14e3b24edcff13dd55aecdb127d0fd5003ec234680ac58f904409991187399d2e80d57543d13ac34d3ee1a453fc7417378e7b8014facdb9ef989e6b0917cd56012ed6602e63643a2918f998a65f8e17a1fffc5d433ad879362bf28c47e72e059faa5d4713a73e399344cebd059a203f9b5b317cc02fdaa81f2ed2e41db1bfa29a73e21c80068ea8ab8b35842c85b7a41b95512551da8768cd0248378eaf715bac2b82f5540111150ef1cae360832ddac01ea6ad1d9019d36ffec46e516d2e170c7d33d8caa395adce28b9138ef3983be4eba366142858b230d46bcc2108f8005db044c66516d01876a1f194d5fde163351965877ca63e54a7163c431519be7717f43f0b4d85a277afd75b91fdd075c4d6d303110535305bc499fc6eab11041be81759e76473a286a85de85e195c95b6537ccdeb852b388337148aa51f62e3909f9b6b1f690e5464f51ad4bc8e2fa38e85fecba6db7921d38f2d98742315ac4631e6e5cf544ee71143b4176e93fcd9a6286869102650c40cc543465bfb2966874561c246ef224d0719aea811b63a91191990213cbfd8ae46b66882396ec84552fe83ca6acb3da83b0a2d13a4dc105b79110fe70d81c70eed6cd4596fda7baf3e6dc68d47bffe5addd554d49e3999cba7c96c2656529e8a5dfc67cc93473a519165a2b58cf441caa42c195d6105b08e89647788b2113d39c6a1f764cb4c908c712ed62a334f70c18341e93a5565c070c89db35e609ebb2d795abfc937579d621d015ea60fd05d246e545f8770f0ee02b051e7a87aa44c2ed5543dc4852e5a8e25ad132041be35c5ebd1df079f537d2b975b740c16fdbd94528bcb24567d6aa104e1370c728184a63aaecca933b50f995acae35cc927b0bfca7983ed923d61b54553a79105cf4e3595c5bc6878367ef52f8c13018d86f380d49dbf5b54cba7e104af3630f9d389d64665c7984296e574aea643958485840fba9b79df765ac64fc2a9442ec286422e0af685e1ab72394934ee2dff1801a58592f6a001fe2293f96033bf5625ed5501bfed210ab35812fe153809ec9f76ef0f08a084b4239aab5c3daa0892523b31c9e27e7e33df18b2e29ad7caa33ae94a2e7c84cb5fa73f51c8ca771dac3b0879c42181abdb1f1cd29c52f1d7ed89778ab32c01b941caaf976f3bfbe31e0bd3dfbb7da45534bdfa773d1a96c7e367c8ce3af5dac8d82347e0b30d3f3e13db8f59169afbea85c2f1a519b4aea6097f2b6d15dae174902cf0294497ca836e782b570b951c705fb485c2ea24f771135f3f21785ebc08f425353bde0fafc19593dee71882f84a4f8ae42a13a0c1ef444ad9ec98e5670a8e48877d285b9e6eef58bd52497b978fb7b33c435976370bc62f86e7746b71114660ab681a5d159d5c62561d72812bbd8ddd6ceb955d1e91c8b284429891fd7f0bcf20570a5a401a51d368c775f81b58b4c55f8c7375ed9c644454a097409c01b2be3a3578f159e3241a9078e45685b60c82f21a05f86b7bcc3ab69e42bf77b341e9e55b9c42e9c4c792f391b686aa29b89d9b575bb13e5f9811770d864a20e28b3d48b8fa47b8b8a4013ee14ba01bc3721ac8186f46d00b61ec0d9c68b3868afb93d23f8e2512cccd2aa493b2953374ddea144e8d202164438e808d73ddfda97bc60198fc63d621d7cf54e39492b83c9b8eaa855b95eb91f7d25add1bdbe462f72d877515deacbefa570c3b1b316b63da7f313f65d0f63f2199c78f9c35c4b997fcb2c3da8c4e5a8208297138ee08d1c48dfad9ab4d7c8fcb66280d349df86038625df9e70ae98c606e3ff095f94b1eb3263cdafc9e401d29e7b801e287de025ac7be7c9faaccd040da519bd479a4caf9b1c14bd9c64972773863a85773045ac068462b82ea43cee9628ae2272c1a8d17fda179a22c64e93a83f040afafbcb9c44cdc0a9d5f12cbbac991708b0cb9b689141ca9fbf2421feb2fadc27ce6948379cbd631c654eb21f3bbc6ff60bab018a9a90e9660eb540f0d16981f16af76a1ad8bb91e67cb810b5d0a3a0e49324e0a75a47a756c3aefab4931c230909b440647984a271546646d2fc67a35c14064a0d1e89a703d5f89cb865c501b3ba55f128fed7c9b0c31beccdbc90c6f0a9e754ce090abdadf5c03edd5ea3991a84fdd1d7bed7f9d8e3872f331081aee35ecd1e81fae67643b38d97cb3a81b97a9d09c73c140dbda2efaf1a8775330e1e56a861a8e517f238b871ced58091933110ab033518ca18edbf8ab6cf80838cb85a06f660ad2ae664e870b4d26bfba90510facaff2ad4ba19715ea6ddfd656f9e414e660f532d4fc43c166f01f150b3400cd85ad1cb9599fd89285a4eec574f1b2ef54af021b990fbf1a01098c2d79d80834769f32cc7e94dfc3237a6f66dfeda8a1ae97d9612e462b787cab6883964709d69ea5a649c9af376799558733c5e88cb91e6544693961bdd03ec1b8f46f8ef725eb5060ebbe41987ab5208992f1a366809675f8265524e6210e636c81dc70a24cd34acd9ab8a3d6b296368d56ee117de991e822f4d897cdad4f10fe8356901e29395afa12d5b2acac38a78afbe938f54161fb7934ef4a0cd1b661434da377befece43524857313a22a32b58d2b68fbbd99fff526793ab2ddecdabac479b14dad1373be54db1c3a125ab4032a6dddb6d38396377d98eb30d1e81edce3aa1683360b02309d8bf40356e685274991ba72e28f045dc0ac670e1bab70b6019ea659a83a5b2d2934fff8302e88ee8c6fec5456a05676c14a53ca7eed5485f4e4ea42198251142aa2cdb5a9d81a0c7a98e2c3c95bf96e73c68c78d467186eeac1dffce3853a6a544a1331bf650758d64bb8fab1104170824894fdd8a284d281aec9d62705683d8f8de11a5845b9d1b5cedb7fd0bd76da3341e75ddd05f261520228664d016b265f881e4b7bd303b7c38b8adcf31a7c211bcddb5260080105c1b70c41ee99d421eefaf1e51d762f245853cc36250bfbb3f547770303dc8a38d03dec2d0a7233b634f83a2456f279126c7fcb47f9301cb2d5e8db69f4721f68000d78cffb39317371748b85e824bd82284875b7bb65604c94ff0b8816cef677ade31963bd514ba59e3d998735a963d018b840a80be11a52ccdadaceb7ed45f0763e6cd49ca6af97f6bde85e4569178192bab4fd59d8c2b1fec181f27a8fa2c17552c16e3b5";
         String expectedSig3 = "000000029200f6d2d656430e2c49eda24923d91c484885f9e626e8e76cad5fd2f7d875ccaa913998ed0c0085cae2d9c2e563315d4df92d857520f22935aca637e8755fe9f62d6389cbc9738ce8cff101e0707d6850ce003b9b884fd3c669647d697bfd3a24b675a6955113297e9ba806b790a776362c47d3081dc09e430172d5d6212893a751c05c70ab1481395e204d1fb675e0dc47e226ca450d132163dcd9dbec52d4df457482e99dcc06c7746eb4ed000eeefd95ee8fdb6d154db279a7f7eeb2aec2f43d566cbbf92bd7c45cd3a56f52223439bfe06a8b136aa35c76381ed5576e8ecc0a25657200ac81e914ce227bc2dc859545d315fce5cae6f88816fde127a76d9cc67953590a63ff7ec230b27658e97a2d5f9a9edcd87b9323381f72d121dc2c56f89312eb58294290415619247ceff45fdf49284ce795592f519288a5c7a86436c190883edab0e58b31eb277ed32cc572e53241394e526496b94f338abdbe84251ae0183edc154fba47af8c0a9e9ad9a2d7ff45aba5987cdb127b344a22b4583cd1926c5791139f4c9852013b0ae8acfd8ce7114ae6d45b321d245a3db19a082d3a3205a6ad3b7838c824bb81952250b09858c543254a42ecfaf23affd642fff84929a4bcb40d65fcea84e461c4b3fc2f8d4a1ba3e561be9805fdfd74741ab4c407252d40188ad932d4b53ac3b806d674a01140f2e97bfb25a3baf30c29b534db198680822a0da9c1a103278e059a8d0dcf5e444178f1a55e3d75ea9bd3147739364960c959667a61645ede0d5fbc8cbb853ab42002a2205fe271b17e9fe095cadad0d168bafa8a392eb8e3e8d2afeacdccbc5a6f4addad61c0b4368f6a92b182c39c2eea41520419bfea541ba2815ba1f539275ce944fb66f94f72c47383350966125927196399bcb39c287889daad0f1dfc41af86bd70429ee6ffda828d4ded50080c47b7315150768eedd9bfe586fdf37d7a0c74807838fafc5a1e5e64feaeba16a1b87cb1f6241af614997ebdfb83681b74ec33b23e4ab6732417ca3b6fb5336b3e31ebe82c90d60aa218d26dd09e65e6542aac5c3f295e3e050e43d68792d10b4c2c5e6c7d0129d85e3ea79e6b13c2af471a2192e8ab8a6f4c96185468e8931b4dac3036e87467d2cf75aec5d908a526a507740e333575ff3902d4142f88bdcb5b06583523197ca8b9ab390fa61e9929ad6a4c73d1ade927f945d45f77175a813623caaf0ff586330ddbc6dba9ee39e62da280ecc13ffc22f83a8cbfe7f3495231a7612b9628e17fc7758f020fa42df2dcedb74b4844481aed37c108bbdd8576f189b662633fa1e91fc481b381f042dd4dbf5e2f6d545b88f1b8c2f04126f62d6f2147aa9001781f930984298edb117598e711702e0788e0e9304c760d9fa1321254290463c5ce096daedae47109657353704ea9809a7e10f2a0cfbee0604098b7f8431a7ec26ad04698b74097b9ec544fe510a29dda677dc2e12e73e57f639e880b0aad0deb5fa29771abfdbb582a76061f3b42c3939c12dd426a03fe9d5287f1bb9f8fcf4f5b2598210ac126c52bcdbe27695432625fbce06960d3460012e52971b5d19fd1b1ee55e52971ce27377dcc4a907bbc7bc2ace94265f22c2089a4c3dda2fd42b3e388e7db905eb043a48ba49104dbbd47c5baae0f1af80a7f414e22799c4e5312a98a72a464ce11850772c4b6bff995be2567dba1364c98b1fa6326b4bfcd4bdf30c48a25f36158875dbd77597987f2bbed2bf0d0ea457726cf108af5628c32fd0f36cf214275aa8961b22fb1e042959d81bff3c1bf2128d4355d0f964dfa6469b5579d63485bf3ba3eb2cea80e095b32ab8665b48c0b1c04895eaea3c67edb71f30bd308971b130eb85a0970b570f47f4e613dfa5e3ee6ea68c6f71cdf369c79146bddb693a1a92b8c80acd061815b34737b5e4adfd68d28e293a3dd85620d6aecf5714e3ea7cd148b9976738b367291ab32de5144a3de66306a248109a43b32d239e80b34ef2994dcb2af1c28f7739f6f7e502a2cba8629cdb74dbab64162782da7b72c0f3d5dc4fbb351eaaa423026842a7fa340fcabb7188487d805abd8f0bbc8dac9d277d3976aef2d300eef62fad9ce676dd243beb43344577510f974a39fe48d922c2a22c6fafebfa549473d9432df34836163cf71dd0a3f337027d2aceafbe27d8bf99657c137567de22bfc060c60c15539b058a6c9eb703be266cde32ffae7eb881a35e4cb453fb5f1a5615ff99107de5b9577bf85d2f00937731a0d96fa7c994a2a69c8ce789bfcd448d2bdcda4191b6ea3ed3b020f78e779f70a1b526026c5d9c0bc15861800e0e71a1b481f0e74069354e08abc6d7d1944bd46596456a4768ac26b555785ebc68f48cff8af0e52b847b7e4d40089a48c741c9adca24754abb1e201020a5d39e7d7b9060a762957060f12d26667bf59cd39b7327e873a7ac774018a0fefef3d22d920ec00cc0d8dc53192306d5306aac11092c750985a226fd6cbe125f68133c12882ee31e48f5c2781e27abefe44cd4a1b4384c42be03218b26722fdf83d6c4490e9bed4a430151206eee84694e5787c94670ef60d5ae8158e78af8797962082fec9b492769231a39f2dfd2dd5e08babc3ecd90d648e8b3acce525680684c37be1ccc9f2abf75b9dc198f33c9dc13bd328ba4cfe1a375a6ddd2fb8b2e44a16ccf87f5eed8247aa49efa0368718ed7de0e3b925ef0d2e362f466763d61baaf81abd468a04c0a164938b77c9293b70ad9ea6fd500d406eafd4e64a716d4480b3861a3f4c1d56df1494877eeff2934690c2e6b03b3d4c618cbca4cfed4e3a5bc0204aa307b023e24f17cf41cb26a52797cf55a94a015a79d1fa5a49df99501f4590522645f54bfd8738b8395b3b7da5f5bdd2b1a2cdbaa21d692e6c8894c9896d42ee55088f4829f07e226a2d06196b15d909a709df1ad319de092e30f3d373595c704192d1b9f6330dc1631bdef5fb01d9ba7c0790cd840f3de0d0cc48b5c6640b3b7729ff9c1dc23e3b44edd9da3615e4e46080fec3123382f7ec23eb2b9c2e37befc6d246af039b5765d039be4aa2007955dfe8862983d252d8b79a33e9b1c531003f1cd3a8bcc6d29ff8aac0062bbc479ad59262cc6bce4048859718988b62e2c4511ed65f659cf8c579a53a6a544a1331bf650758d64bb8fab1104170824894fdd8a284d281aec9d62705683d8f8de11a5845b9d1b5cedb7fd0bd76da3341e75ddd05f261520228664d016b265f881e4b7bd303b7c38b8adcf31a7c211bcddb5260080105c1b70c41ee99d421eefaf1e51d762f245853cc36250bfbb3f547770303dc8a38d03dec2d0a7233b634f83a2456f279126c7fcb47f9301cb2d5e8db69f4721f68000d78cffb39317371748b85e824bd82284875b7bb65604c94ff0b8816cef677ade31963bd514ba59e3d998735a963d018b840a80be11a52ccdadaceb7ed45f0763e6cd49ca6af97f6bde85e4569178192bab4fd59d8c2b1fec181f27a8fa2c17552c16e3b5";
+
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedSig1), sig1));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedSig2), sig2));
         assertEquals(true, Arrays.areEqual(Hex.decode(expectedSig3), sig3));
@@ -215,6 +223,7 @@
     }
 
     public void testVerifySignatureSHA256()
+        throws Exception
     {
         XMSSParameters params = new XMSSParameters(4, new SHA256Digest());
         XMSS xmss = new XMSS(params, new NullPRNG());
@@ -223,7 +232,7 @@
 
         for (int i = 0; i < 3; i++)
         {
-            byte[] publicKey = xmss.exportPublicKey();
+            byte[] publicKey = xmss.exportPublicKey().getEncoded();
             xmss.sign(msg1);
             byte[] signature = xmss.sign(msg1);
             try
@@ -250,6 +259,7 @@
     }
 
     public void testVerifySignatureSHA512()
+        throws Exception
     {
         XMSSParameters params = new XMSSParameters(4, new SHA512Digest());
         XMSS xmss = new XMSS(params, new NullPRNG());
@@ -258,7 +268,7 @@
 
         for (int i = 0; i < 3; i++)
         {
-            byte[] publicKey = xmss.exportPublicKey();
+            byte[] publicKey = xmss.exportPublicKey().getEncoded();
             xmss.sign(msg1);
             byte[] signature = xmss.sign(msg1);
             try
@@ -298,13 +308,13 @@
         Arrays.fill(msg3, (byte)0xcc);
         byte[] signature1 = xmss1.sign(msg1);
         byte[] signature2 = xmss1.sign(msg2);
-        byte[] exportedPrivateKey = xmss1.exportPrivateKey();
-        byte[] exportedPublicKey = xmss1.exportPublicKey();
+        byte[] exportedPrivateKey = xmss1.exportPrivateKey().getEncoded();
+        byte[] exportedPublicKey = xmss1.exportPublicKey().getEncoded();
         byte[] signature3 = xmss1.sign(msg3);
 
         XMSS xmss2 = new XMSS(params, new NullPRNG());
 
-            xmss2.importState(exportedPrivateKey, exportedPublicKey);
+        xmss2.importState(exportedPrivateKey, exportedPublicKey);
 
         byte[] signature4 = xmss2.sign(msg3);
         assertEquals(true, Arrays.areEqual(signature3, signature4));
@@ -338,8 +348,8 @@
         Arrays.fill(msg3, (byte)0xcc);
         byte[] signature1 = xmss1.sign(msg1);
         byte[] signature2 = xmss1.sign(msg2);
-        byte[] exportedPrivateKey = xmss1.exportPrivateKey();
-        byte[] exportedPublicKey = xmss1.exportPublicKey();
+        byte[] exportedPrivateKey = xmss1.exportPrivateKey().getEncoded();
+        byte[] exportedPublicKey = xmss1.exportPublicKey().getEncoded();
         byte[] signature3 = xmss1.sign(msg3);
 
         XMSS xmss2 = new XMSS(params, new NullPRNG());
@@ -370,7 +380,7 @@
         XMSSParameters params = new XMSSParameters(4, new SHA512Digest());
         XMSS xmss1 = new XMSS(params, new SecureRandom());
         xmss1.generateKeys();
-        byte[] publicKey = xmss1.exportPublicKey();
+        byte[] publicKey = xmss1.exportPublicKey().getEncoded();
         byte[] message = new byte[1024];
 
         for (int i = 0; i < 5; i++)
@@ -378,7 +388,7 @@
             xmss1.sign(message);
         }
         byte[] signature = xmss1.sign(message);
-        assertTrue(Arrays.areEqual(publicKey, xmss1.exportPublicKey()));
+        assertTrue(Arrays.areEqual(publicKey, xmss1.exportPublicKey().getEncoded()));
         try
         {
             xmss1.verifySignature(message, signature, publicKey);
@@ -388,9 +398,9 @@
             e.printStackTrace();
             fail();
         }
-        assertTrue(Arrays.areEqual(publicKey, xmss1.exportPublicKey()));
+        assertTrue(Arrays.areEqual(publicKey, xmss1.exportPublicKey().getEncoded()));
         xmss1.sign(message);
-        byte[] privateKey7 = xmss1.exportPrivateKey();
+        byte[] privateKey7 = xmss1.exportPrivateKey().getEncoded();
         try
         {
             xmss1.verifySignature(message, signature, publicKey);
@@ -400,7 +410,7 @@
             e.printStackTrace();
             fail();
         }
-        assertTrue(Arrays.areEqual(privateKey7, xmss1.exportPrivateKey()));
+        assertTrue(Arrays.areEqual(privateKey7, xmss1.exportPrivateKey().getEncoded()));
         byte[] signature7 = xmss1.sign(message);
 
         xmss1.importState(privateKey7, publicKey);
@@ -437,8 +447,8 @@
         XMSSParameters params = new XMSSParameters(4, new SHA256Digest());
         XMSS xmss = new XMSS(params, new SecureRandom());
         xmss.generateKeys();
-        byte[] exportedPrivateKey = xmss.exportPrivateKey();
-        byte[] exportedPublicKey = xmss.exportPublicKey();
+        byte[] exportedPrivateKey = xmss.exportPrivateKey().getEncoded();
+        byte[] exportedPublicKey = xmss.exportPublicKey().getEncoded();
 
         xmss.importState(exportedPrivateKey, exportedPublicKey);
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyFactory.java
index 1bbddf3..1a42248 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyFactory.java
@@ -3,11 +3,13 @@
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.bouncycastle.asn1.ASN1BitString;
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
 import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
@@ -17,6 +19,8 @@
 import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
 import org.bouncycastle.pqc.asn1.XMSSMTPrivateKey;
 import org.bouncycastle.pqc.asn1.XMSSPrivateKey;
+import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.newhope.NHPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLAPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.sphincs.SPHINCSPrivateKeyParameters;
@@ -27,6 +31,7 @@
 import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
 import org.bouncycastle.pqc.crypto.xmss.XMSSPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.xmss.XMSSUtil;
+import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Pack;
 
 /**
@@ -86,6 +91,32 @@
         {
             return new NHPrivateKeyParameters(convert(ASN1OctetString.getInstance(keyInfo.parsePrivateKey()).getOctets()));
         }
+        else if (algOID.equals(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig))
+        {
+            byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePrivateKey()).getOctets();
+            ASN1BitString pubKey = keyInfo.getPublicKeyData();
+
+            if (Pack.bigEndianToInt(keyEnc, 0) == 1)
+            {
+                if (pubKey != null)
+                {
+                    byte[] pubEnc = pubKey.getOctets();
+
+                    return LMSPrivateKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length), Arrays.copyOfRange(pubEnc, 4, pubEnc.length));
+                }
+                return LMSPrivateKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length));
+            }
+            else
+            {
+                if (pubKey != null)
+                {
+                    byte[] pubEnc = pubKey.getOctets();
+
+                    return HSSPrivateKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length), pubEnc);
+                }
+                return HSSPrivateKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length));
+            }
+        }
         else if (algOID.equals(BCObjectIdentifiers.xmss))
         {
             XMSSKeyParams keyParams = XMSSKeyParams.getInstance(keyInfo.getPrivateKeyAlgorithm().getParameters());
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java
index 7873f85..629afef 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java
@@ -4,6 +4,7 @@
 
 import org.bouncycastle.asn1.ASN1Set;
 import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
@@ -13,6 +14,9 @@
 import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
 import org.bouncycastle.pqc.asn1.XMSSMTPrivateKey;
 import org.bouncycastle.pqc.asn1.XMSSPrivateKey;
+import org.bouncycastle.pqc.crypto.lms.Composer;
+import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.newhope.NHPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLAPrivateKeyParameters;
 import org.bouncycastle.pqc.crypto.sphincs.SPHINCSPrivateKeyParameters;
@@ -87,6 +91,26 @@
 
             return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(octets));
         }
+        else if (privateKey instanceof LMSPrivateKeyParameters)
+        {
+            LMSPrivateKeyParameters params = (LMSPrivateKeyParameters)privateKey;
+
+            byte[] encoding = Composer.compose().u32str(1).bytes(params).build();
+            byte[] pubEncoding = Composer.compose().u32str(1).bytes(params.getPublicKey()).build();
+
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
+            return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(encoding), attributes, pubEncoding);
+        }
+        else if (privateKey instanceof HSSPrivateKeyParameters)
+        {
+            HSSPrivateKeyParameters params = (HSSPrivateKeyParameters)privateKey;
+
+            byte[] encoding = Composer.compose().u32str(params.getL()).bytes(params).build();
+            byte[] pubEncoding = Composer.compose().u32str(params.getL()).bytes(params.getPublicKey().getLMSPublicKey()).build();
+
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
+            return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(encoding), attributes, pubEncoding);
+        }
         else if (privateKey instanceof XMSSPrivateKeyParameters)
         {
             XMSSPrivateKeyParameters keyParams = (XMSSPrivateKeyParameters)privateKey;
@@ -94,7 +118,7 @@
                 new XMSSKeyParams(keyParams.getParameters().getHeight(),
                     Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
 
-            return new PrivateKeyInfo(algorithmIdentifier, xmssCreateKeyStructure(keyParams));
+            return new PrivateKeyInfo(algorithmIdentifier, xmssCreateKeyStructure(keyParams), attributes);
         }
         else if (privateKey instanceof XMSSMTPrivateKeyParameters)
         {
@@ -103,7 +127,7 @@
                 new XMSSMTKeyParams(keyParams.getParameters().getHeight(), keyParams.getParameters().getLayers(),
                     Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
 
-            return new PrivateKeyInfo(algorithmIdentifier, xmssmtCreateKeyStructure(keyParams));
+            return new PrivateKeyInfo(algorithmIdentifier, xmssmtCreateKeyStructure(keyParams), attributes);
         }
         else
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PublicKeyFactory.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PublicKeyFactory.java
index 1024697..2462258 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PublicKeyFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/PublicKeyFactory.java
@@ -7,7 +7,10 @@
 
 import org.bouncycastle.asn1.ASN1InputStream;
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
 import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
@@ -16,6 +19,8 @@
 import org.bouncycastle.pqc.asn1.XMSSKeyParams;
 import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
 import org.bouncycastle.pqc.asn1.XMSSPublicKey;
+import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.newhope.NHPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLAPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.sphincs.SPHINCSPublicKeyParameters;
@@ -23,6 +28,8 @@
 import org.bouncycastle.pqc.crypto.xmss.XMSSMTPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.xmss.XMSSParameters;
 import org.bouncycastle.pqc.crypto.xmss.XMSSPublicKeyParameters;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Pack;
 
 /**
  * Factory to create asymmetric public key parameters for asymmetric ciphers from range of
@@ -40,6 +47,9 @@
         converters.put(PQCObjectIdentifiers.newHope, new NHConverter());
         converters.put(PQCObjectIdentifiers.xmss, new XMSSConverter());
         converters.put(PQCObjectIdentifiers.xmss_mt, new XMSSMTConverter());
+        converters.put(IsaraObjectIdentifiers.id_alg_xmss, new XMSSConverter());
+        converters.put(IsaraObjectIdentifiers.id_alg_xmssmt, new XMSSMTConverter());
+        converters.put(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, new LMSConverter());
     }
 
     /**
@@ -84,7 +94,7 @@
     /**
      * Create a public key from the passed in SubjectPublicKeyInfo
      *
-     * @param keyInfo the SubjectPublicKeyInfo containing the key data
+     * @param keyInfo       the SubjectPublicKeyInfo containing the key data
      * @param defaultParams default parameters that might be needed.
      * @return the appropriate key parameter
      * @throws IOException on an error decoding the key
@@ -128,7 +138,7 @@
             throws IOException
         {
             return new SPHINCSPublicKeyParameters(keyInfo.getPublicKeyData().getBytes(),
-                            Utils.sphincs256LookupTreeAlgName(SPHINCS256KeyParams.getInstance(keyInfo.getAlgorithm().getParameters())));
+                Utils.sphincs256LookupTreeAlgName(SPHINCS256KeyParams.getInstance(keyInfo.getAlgorithm().getParameters())));
         }
     }
 
@@ -149,13 +159,25 @@
             throws IOException
         {
             XMSSKeyParams keyParams = XMSSKeyParams.getInstance(keyInfo.getAlgorithm().getParameters());
-            ASN1ObjectIdentifier treeDigest = keyParams.getTreeDigest().getAlgorithm();
-            XMSSPublicKey xmssPublicKey = XMSSPublicKey.getInstance(keyInfo.parsePublicKey());
 
-            return new XMSSPublicKeyParameters
-                .Builder(new XMSSParameters(keyParams.getHeight(), Utils.getDigest(treeDigest)))
-                .withPublicSeed(xmssPublicKey.getPublicSeed())
-                .withRoot(xmssPublicKey.getRoot()).build();
+            if (keyParams != null)
+            {
+                ASN1ObjectIdentifier treeDigest = keyParams.getTreeDigest().getAlgorithm();
+                XMSSPublicKey xmssPublicKey = XMSSPublicKey.getInstance(keyInfo.parsePublicKey());
+
+                return new XMSSPublicKeyParameters
+                    .Builder(new XMSSParameters(keyParams.getHeight(), Utils.getDigest(treeDigest)))
+                    .withPublicSeed(xmssPublicKey.getPublicSeed())
+                    .withRoot(xmssPublicKey.getRoot()).build();
+            }
+            else
+            {
+                byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
+
+                return new XMSSPublicKeyParameters
+                    .Builder(XMSSParameters.lookupByOID(Pack.bigEndianToInt(keyEnc, 0)))
+                    .withPublicKey(keyEnc).build();
+            }
         }
     }
 
@@ -166,14 +188,50 @@
             throws IOException
         {
             XMSSMTKeyParams keyParams = XMSSMTKeyParams.getInstance(keyInfo.getAlgorithm().getParameters());
-            ASN1ObjectIdentifier treeDigest = keyParams.getTreeDigest().getAlgorithm();
 
-            XMSSPublicKey xmssMtPublicKey = XMSSPublicKey.getInstance(keyInfo.parsePublicKey());
+            if (keyParams != null)
+            {
+                ASN1ObjectIdentifier treeDigest = keyParams.getTreeDigest().getAlgorithm();
 
-            return new XMSSMTPublicKeyParameters
-                .Builder(new XMSSMTParameters(keyParams.getHeight(), keyParams.getLayers(), Utils.getDigest(treeDigest)))
-                .withPublicSeed(xmssMtPublicKey.getPublicSeed())
-                .withRoot(xmssMtPublicKey.getRoot()).build();
+                XMSSPublicKey xmssMtPublicKey = XMSSPublicKey.getInstance(keyInfo.parsePublicKey());
+
+                return new XMSSMTPublicKeyParameters
+                    .Builder(new XMSSMTParameters(keyParams.getHeight(), keyParams.getLayers(), Utils.getDigest(treeDigest)))
+                    .withPublicSeed(xmssMtPublicKey.getPublicSeed())
+                    .withRoot(xmssMtPublicKey.getRoot()).build();
+            }
+            else
+            {
+                byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
+
+                return new XMSSMTPublicKeyParameters
+                    .Builder(XMSSMTParameters.lookupByOID(Pack.bigEndianToInt(keyEnc, 0)))
+                    .withPublicKey(keyEnc).build();
+            }
+        }
+    }
+
+    private static class LMSConverter
+        extends SubjectPublicKeyInfoConverter
+    {
+        AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
+            throws IOException
+        {
+            byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
+
+            if (Pack.bigEndianToInt(keyEnc, 0) == 1)
+            {
+                return LMSPublicKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length));
+            }
+            else
+            {
+                // public key with extra tree height
+                if (keyEnc.length == 64)
+                {
+                    keyEnc = Arrays.copyOfRange(keyEnc, 4, keyEnc.length);
+                }
+                return HSSPublicKeyParameters.getInstance(keyEnc);
+            }
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/SubjectPublicKeyInfoFactory.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/SubjectPublicKeyInfoFactory.java
index 4ccbf4c..180f3bf 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/SubjectPublicKeyInfoFactory.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/util/SubjectPublicKeyInfoFactory.java
@@ -2,6 +2,9 @@
 
 import java.io.IOException;
 
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
@@ -11,6 +14,9 @@
 import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
 import org.bouncycastle.pqc.asn1.XMSSMTPublicKey;
 import org.bouncycastle.pqc.asn1.XMSSPublicKey;
+import org.bouncycastle.pqc.crypto.lms.Composer;
+import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.newhope.NHPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.qtesla.QTESLAPublicKeyParameters;
 import org.bouncycastle.pqc.crypto.sphincs.SPHINCSPublicKeyParameters;
@@ -59,21 +65,64 @@
             AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.newHope);
             return new SubjectPublicKeyInfo(algorithmIdentifier, params.getPubData());
         }
+        else if (publicKey instanceof LMSPublicKeyParameters)
+        {
+            LMSPublicKeyParameters params = (LMSPublicKeyParameters)publicKey;
+
+            byte[] encoding = Composer.compose().u32str(1).bytes(params).build();
+
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
+            return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(encoding));
+        }
+        else if (publicKey instanceof HSSPublicKeyParameters)
+        {
+            HSSPublicKeyParameters params = (HSSPublicKeyParameters)publicKey;
+
+            byte[] encoding = Composer.compose().u32str(params.getL()).bytes(params.getLMSPublicKey()).build();
+
+            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
+            return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(encoding));
+        }
         else if (publicKey instanceof XMSSPublicKeyParameters)
         {
             XMSSPublicKeyParameters keyParams = (XMSSPublicKeyParameters)publicKey;
 
-            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.xmss,
-                new XMSSKeyParams(keyParams.getParameters().getHeight(), Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
-            return new SubjectPublicKeyInfo(algorithmIdentifier, new XMSSPublicKey(keyParams.getPublicSeed(), keyParams.getRoot()));
+            byte[] publicSeed = keyParams.getPublicSeed();
+            byte[] root = keyParams.getRoot();
+            byte[] keyEnc = keyParams.getEncoded();
+            if (keyEnc.length > publicSeed.length + root.length)
+            {
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(IsaraObjectIdentifiers.id_alg_xmss);
+
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(keyEnc));
+            }
+            else
+            {
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.xmss,
+                    new XMSSKeyParams(keyParams.getParameters().getHeight(), Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
+
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new XMSSPublicKey(publicSeed, root));
+            }
         }
         else if (publicKey instanceof XMSSMTPublicKeyParameters)
         {
             XMSSMTPublicKeyParameters keyParams = (XMSSMTPublicKeyParameters)publicKey;
 
-            AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.xmss_mt, new XMSSMTKeyParams(keyParams.getParameters().getHeight(), keyParams.getParameters().getLayers(),
-                Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
-            return new SubjectPublicKeyInfo(algorithmIdentifier, new XMSSMTPublicKey(keyParams.getPublicSeed(), keyParams.getRoot()));
+            byte[] publicSeed = keyParams.getPublicSeed();
+            byte[] root = keyParams.getRoot();
+            byte[] keyEnc = keyParams.getEncoded();
+            if (keyEnc.length > publicSeed.length + root.length)
+            {
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(IsaraObjectIdentifiers.id_alg_xmssmt);
+
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(keyEnc));
+            }
+            else
+            {
+                AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PQCObjectIdentifiers.xmss_mt, new XMSSMTKeyParams(keyParams.getParameters().getHeight(), keyParams.getParameters().getLayers(),
+                    Utils.xmssLookupTreeAlgID(keyParams.getTreeDigest())));
+                return new SubjectPublicKeyInfo(algorithmIdentifier, new XMSSMTPublicKey(keyParams.getPublicSeed(), keyParams.getRoot()));
+            }
         }
         else
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/DigestUtil.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/DigestUtil.java
index 874ba8a..c15acdc 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/DigestUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/DigestUtil.java
@@ -14,6 +14,7 @@
 class DigestUtil
 {
     private static Map<String, ASN1ObjectIdentifier> nameToOid = new HashMap<String, ASN1ObjectIdentifier>();
+    private static Map<ASN1ObjectIdentifier, String> oidToName = new HashMap<ASN1ObjectIdentifier, String>();
 
     static
     {
@@ -21,6 +22,11 @@
         nameToOid.put("SHA-512", NISTObjectIdentifiers.id_sha512);
         nameToOid.put("SHAKE128", NISTObjectIdentifiers.id_shake128);
         nameToOid.put("SHAKE256", NISTObjectIdentifiers.id_shake256);
+
+        oidToName.put(NISTObjectIdentifiers.id_sha256, "SHA-256");
+        oidToName.put(NISTObjectIdentifiers.id_sha512, "SHA-512");
+        oidToName.put(NISTObjectIdentifiers.id_shake128, "SHAKE128");
+        oidToName.put(NISTObjectIdentifiers.id_shake256, "SHAKE256");
     }
 
     static Digest getDigest(ASN1ObjectIdentifier oid)
@@ -45,6 +51,17 @@
         throw new IllegalArgumentException("unrecognized digest OID: " + oid);
     }
 
+    static String getDigestName(ASN1ObjectIdentifier oid)
+    {
+        String name = oidToName.get(oid);
+        if (name != null)
+        {
+            return name;
+        }
+
+        throw new IllegalArgumentException("unrecognized digest oid: " + oid);
+    }
+
     static ASN1ObjectIdentifier getDigestOID(String name)
     {
         ASN1ObjectIdentifier oid = nameToOid.get(name);
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlus.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlus.java
index 4c36448..57dd58a 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlus.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/WOTSPlus.java
@@ -34,7 +34,7 @@
      *
      * @param params Parameters for WOTSPlus object.
      */
-    protected WOTSPlus(WOTSPlusParameters params)
+    WOTSPlus(WOTSPlusParameters params)
     {
         super();
         if (params == null)
@@ -83,7 +83,7 @@
      * @param otsHashAddress OTS hash address for randomization.
      * @return WOTS+ signature.
      */
-    protected WOTSPlusSignature sign(byte[] messageDigest, OTSHashAddress otsHashAddress)
+    WOTSPlusSignature sign(byte[] messageDigest, OTSHashAddress otsHashAddress)
     {
         if (messageDigest == null)
         {
@@ -128,38 +128,6 @@
     }
 
     /**
-     * Verifies signature on message.
-     *
-     * @param messageDigest  The digest that was signed.
-     * @param signature      Signature on digest.
-     * @param otsHashAddress OTS hash address for randomization.
-     * @return true if signature was correct false else.
-     */
-    protected boolean verifySignature(byte[] messageDigest, WOTSPlusSignature signature,
-                                      OTSHashAddress otsHashAddress)
-    {
-        if (messageDigest == null)
-        {
-            throw new NullPointerException("messageDigest == null");
-        }
-        if (messageDigest.length != params.getTreeDigestSize())
-        {
-            throw new IllegalArgumentException("size of messageDigest needs to be equal to size of digest");
-        }
-        if (signature == null)
-        {
-            throw new NullPointerException("signature == null");
-        }
-        if (otsHashAddress == null)
-        {
-            throw new NullPointerException("otsHashAddress == null");
-        }
-        byte[][] tmpPublicKey = getPublicKeyFromSignature(messageDigest, signature, otsHashAddress).toByteArray();
-        /* compare values */
-        return XMSSUtil.areEqual(tmpPublicKey, getPublicKey(otsHashAddress).toByteArray()) ? true : false;
-    }
-
-    /**
      * Calculates a public key based on digest and signature.
      *
      * @param messageDigest  The digest that was signed.
@@ -167,7 +135,7 @@
      * @param otsHashAddress OTS hash address for randomization.
      * @return WOTS+ public key derived from digest and signature.
      */
-    protected WOTSPlusPublicKeyParameters getPublicKeyFromSignature(byte[] messageDigest, WOTSPlusSignature signature,
+    WOTSPlusPublicKeyParameters getPublicKeyFromSignature(byte[] messageDigest, WOTSPlusSignature signature,
                                                                     OTSHashAddress otsHashAddress)
     {
         if (messageDigest == null)
@@ -318,7 +286,7 @@
      * Derive WOTS+ secret key for specific index as in XMSS ref impl Andreas
      * Huelsing.
      *
-     * @param otsHashAddress
+     * @param otsHashAddress one time hash address.
      * @return WOTS+ secret key at index.
      */
     protected byte[] getWOTSPlusSecretKey(byte[] secretKeySeed, OTSHashAddress otsHashAddress)
@@ -406,7 +374,7 @@
      * @param otsHashAddress OTS hash address for randomization.
      * @return WOTS+ public key.
      */
-    protected WOTSPlusPublicKeyParameters getPublicKey(OTSHashAddress otsHashAddress)
+    WOTSPlusPublicKeyParameters getPublicKey(OTSHashAddress otsHashAddress)
     {
         if (otsHashAddress == null)
         {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSKeyPairGenerator.java
index 8ff90b1..32123f7 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSKeyPairGenerator.java
@@ -3,12 +3,14 @@
 import java.security.SecureRandom;
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 
 /**
  * Key pair generator for XMSS keys.
  */
 public final class XMSSKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
 {
     private XMSSParameters params;
     private SecureRandom prng;
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTKeyPairGenerator.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTKeyPairGenerator.java
index a0636e3..66a1271 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTKeyPairGenerator.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTKeyPairGenerator.java
@@ -3,12 +3,14 @@
 import java.security.SecureRandom;
 
 import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
 import org.bouncycastle.crypto.KeyGenerationParameters;
 
 /**
  * Key pair generator for XMSS^MT keys.
  */
 public final class XMSSMTKeyPairGenerator
+    implements AsymmetricCipherKeyPairGenerator
 {
     private XMSSMTParameters params;
     private XMSSParameters xmssParams;
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTParameters.java
index a8f7c9d..775c85d 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSMTParameters.java
@@ -1,12 +1,60 @@
 package org.bouncycastle.pqc.crypto.xmss;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Integers;
 
 /**
  * XMSS^MT Parameters.
  */
 public final class XMSSMTParameters
 {
+    private static final Map<Integer, XMSSMTParameters> paramsLookupTable;
+
+    static
+    {
+        Map<Integer, XMSSMTParameters> pMap = new HashMap<Integer, XMSSMTParameters>();
+
+        pMap.put(Integers.valueOf(0x00000001), new XMSSMTParameters(20, 2, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000002), new XMSSMTParameters(20, 4, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000003), new XMSSMTParameters(40, 2, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000004), new XMSSMTParameters(40, 4, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000005), new XMSSMTParameters(40, 8, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000006), new XMSSMTParameters(60, 3, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000007), new XMSSMTParameters(60, 6, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000008), new XMSSMTParameters(60, 12, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000009), new XMSSMTParameters(20, 2, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000a), new XMSSMTParameters(20, 4, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000b), new XMSSMTParameters(40, 2, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000c), new XMSSMTParameters(40, 4, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000d), new XMSSMTParameters(40, 8, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000e), new XMSSMTParameters(60, 3, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x0000000f), new XMSSMTParameters(60, 6, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x00000010), new XMSSMTParameters(60, 12, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x00000011), new XMSSMTParameters(20, 2, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000012), new XMSSMTParameters(20, 4, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000013), new XMSSMTParameters(40, 2, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000014), new XMSSMTParameters(40, 4, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000015), new XMSSMTParameters(40, 8, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000016), new XMSSMTParameters(60, 3, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000017), new XMSSMTParameters(60, 6, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000018), new XMSSMTParameters(60, 12, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000019), new XMSSMTParameters(20, 2, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001a), new XMSSMTParameters(20, 4, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001b), new XMSSMTParameters(40, 2, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001c), new XMSSMTParameters(40, 4, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001d), new XMSSMTParameters(40, 8, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001e), new XMSSMTParameters(60, 3, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000001f), new XMSSMTParameters(60, 6, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x00000020), new XMSSMTParameters(60, 12, NISTObjectIdentifiers.id_shake256));
+        paramsLookupTable = Collections.unmodifiableMap(pMap);
+    }
+
     private final XMSSOid oid;
     private final XMSSParameters xmssParams;
     private final int height;
@@ -21,10 +69,22 @@
      */
     public XMSSMTParameters(int height, int layers, Digest digest)
     {
+        this(height, layers, DigestUtil.getDigestOID(digest.getAlgorithmName()));
+    }
+
+    /**
+     * XMSSMT constructor...
+     *
+     * @param height Height of tree.
+     * @param layers Amount of layers.
+     * @param digestOID Object identifier of digest to use.
+     */
+    public XMSSMTParameters(int height, int layers, ASN1ObjectIdentifier digestOID)
+    {
         super();
         this.height = height;
         this.layers = layers;
-        this.xmssParams = new XMSSParameters(xmssTreeHeight(height, layers), digest);
+        this.xmssParams = new XMSSParameters(xmssTreeHeight(height, layers), digestOID);
         oid = DefaultXMSSMTOid.lookup(getTreeDigest(), getTreeDigestSize(), getWinternitzParameter(),
             getLen(), getHeight(), layers);
         /*
@@ -96,6 +156,16 @@
     }
 
     /**
+     * Return the tree digest OID.
+     *
+     * @return OID for digest used to build the tree.
+     */
+    public ASN1ObjectIdentifier getTreeDigestOID()
+    {
+        return xmssParams.getTreeDigestOID();
+    }
+
+    /**
      * Getter Winternitz parameter.
      *
      * @return Winternitz parameter.
@@ -114,4 +184,9 @@
     {
         return oid;
     }
+
+    public static XMSSMTParameters lookupByOID(int oid)
+    {
+        return paramsLookupTable.get(Integers.valueOf(oid));
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSParameters.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSParameters.java
index d5291e9..c2b6363 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSParameters.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSParameters.java
@@ -1,13 +1,39 @@
 package org.bouncycastle.pqc.crypto.xmss;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
 import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.util.Integers;
 
 /**
  * XMSS Parameters.
  */
 public final class XMSSParameters
 {
+    private static final Map<Integer, XMSSParameters> paramsLookupTable;
+
+    static
+    {
+        Map<Integer, XMSSParameters> pMap = new HashMap<Integer, XMSSParameters>();
+        pMap.put(Integers.valueOf(0x00000001), new XMSSParameters(10, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000002), new XMSSParameters(16, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000003), new XMSSParameters(20, NISTObjectIdentifiers.id_sha256));
+        pMap.put(Integers.valueOf(0x00000004), new XMSSParameters(10, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x00000005), new XMSSParameters(16, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x00000006), new XMSSParameters(20, NISTObjectIdentifiers.id_sha512));
+        pMap.put(Integers.valueOf(0x00000007), new XMSSParameters(10, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000008), new XMSSParameters(16, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x00000009), new XMSSParameters(20, NISTObjectIdentifiers.id_shake128));
+        pMap.put(Integers.valueOf(0x0000000a), new XMSSParameters(10, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000000b), new XMSSParameters(16, NISTObjectIdentifiers.id_shake256));
+        pMap.put(Integers.valueOf(0x0000000c), new XMSSParameters(20, NISTObjectIdentifiers.id_shake256));
+        paramsLookupTable = Collections.unmodifiableMap(pMap);
+    }
+
     private final XMSSOid oid;
     private final int height;
     private final int k;
@@ -20,33 +46,44 @@
     /**
      * XMSS Constructor...
      *
-     * @param height Height of tree.
+     * @param height     Height of tree.
      * @param treeDigest Digest to use.
      */
     public XMSSParameters(int height, Digest treeDigest)
     {
+        this(height, DigestUtil.getDigestOID(treeDigest.getAlgorithmName()));
+    }
+
+    /**
+     * XMSS Constructor...
+     *
+     * @param height     Height of tree.
+     * @param treeDigestOID OID of digest to use.
+     */
+    public XMSSParameters(int height, ASN1ObjectIdentifier treeDigestOID)
+    {
         super();
         if (height < 2)
         {
             throw new IllegalArgumentException("height must be >= 2");
         }
-        if (treeDigest == null)
+        if (treeDigestOID == null)
         {
             throw new NullPointerException("digest == null");
         }
 
         this.height = height;
         this.k = determineMinK();
-        this.treeDigest = treeDigest.getAlgorithmName();
-        this.treeDigestOID = DigestUtil.getDigestOID(treeDigest.getAlgorithmName());
+        this.treeDigest = DigestUtil.getDigestName(treeDigestOID);
+        this.treeDigestOID = treeDigestOID;
 
         this.wotsPlusParams = new WOTSPlusParameters(treeDigestOID);
         this.treeDigestSize = wotsPlusParams.getTreeDigestSize();
         this.winternitzParameter = wotsPlusParams.getWinternitzParameter();
         this.oid = DefaultXMSSOid.lookup(this.treeDigest, this.treeDigestSize, this.winternitzParameter, wotsPlusParams.getLen(), height);
         /*
-		 * if (oid == null) { throw new InvalidParameterException(); }
-		 */
+         * if (oid == null) { throw new InvalidParameterException(); }
+         */
     }
 
     private int determineMinK()
@@ -72,6 +109,16 @@
     }
 
     /**
+     * Return the tree digest OID.
+     *
+     * @return OID for digest used to build the tree.
+     */
+    public ASN1ObjectIdentifier getTreeDigestOID()
+    {
+        return treeDigestOID;
+    }
+
+    /**
      * Getter height.
      *
      * @return XMSS tree height.
@@ -86,10 +133,7 @@
         return treeDigest;
     }
 
-    ASN1ObjectIdentifier getTreeDigestOID()
-    {
-        return treeDigestOID;
-    }
+
 
     int getLen()
     {
@@ -120,4 +164,9 @@
     {
         return k;
     }
+
+    public static XMSSParameters lookupByOID(int oid)
+    {
+        return paramsLookupTable.get(Integers.valueOf(oid));
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSSigner.java b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSSigner.java
index 54b2443..84d2e00 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSSigner.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/crypto/xmss/XMSSSigner.java
@@ -2,6 +2,7 @@
 
 import org.bouncycastle.crypto.CipherParameters;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
 import org.bouncycastle.pqc.crypto.StateAwareMessageSigner;
 import org.bouncycastle.util.Arrays;
 
@@ -60,7 +61,7 @@
         {
             if (privateKey.getUsagesRemaining() <= 0)
             {
-                throw new IllegalStateException("no usages of private key remaining");
+                throw new ExhaustedPrivateKeyException("no usages of private key remaining");
             }
             if (privateKey.getBDSState().getAuthenticationPath().isEmpty())
             {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSKey.java
new file mode 100644
index 0000000..9da3e98
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSKey.java
@@ -0,0 +1,17 @@
+package org.bouncycastle.pqc.jcajce.interfaces;
+
+import java.security.Key;
+
+/**
+ * Base interface for Leighton-Micali Hash-Based Signatures (LMS) keys.
+ */
+public interface LMSKey
+    extends Key
+{
+    /**
+     * Return the number of levels (L) associated with the key.
+     *
+     * @return L.
+     */
+    int getLevels();
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSPrivateKey.java
new file mode 100644
index 0000000..d9ea316
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/LMSPrivateKey.java
@@ -0,0 +1,34 @@
+package org.bouncycastle.pqc.jcajce.interfaces;
+
+import java.security.PrivateKey;
+
+/**
+ * Base interface for an LMS private key
+ */
+public interface LMSPrivateKey
+    extends LMSKey, PrivateKey
+{
+    /**
+     * Return the index of the next signature.
+     *
+     * @return the index number for the next signature.
+     */
+    long getIndex();
+
+    /**
+     * Return the number of usages left for the private key.
+     *
+     * @return the number of times the key can be used before it is exhausted.
+     */
+    long getUsagesRemaining();
+
+    /**
+     * Return a key representing a shard of the key space that can be used usageCount times.
+     * <p>
+     * Note: this will use the range [index...index + usageCount) for the current key.
+     * </p>
+     * @param usageCount the number of usages the key should have.
+     * @return a key based on the current key that can be used usageCount times.
+     */
+    LMSPrivateKey extractKeyShard(int usageCount);
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSMTPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSMTPrivateKey.java
index 848d0f7..3390d40 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSMTPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSMTPrivateKey.java
@@ -9,6 +9,13 @@
     extends XMSSMTKey, PrivateKey
 {
     /**
+     * Return the index of the next signature.
+     *
+     * @return the index number for the next signature.
+     */
+    long getIndex();
+
+    /**
      * Return the number of usages left for the private key.
      *
      * @return the number of times the key can be used before it is exhausted.
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSPrivateKey.java
index 5df12bd..915aa9f 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/interfaces/XMSSPrivateKey.java
@@ -9,6 +9,13 @@
     extends XMSSKey, PrivateKey
 {
     /**
+     * Return the index of the next signature.
+     *
+     * @return the index number for the next signature.
+     */
+    long getIndex();
+
+    /**
      * Return the number of usages left for the private key.
      *
      * @return the number of times the key can be used before it is exhausted.
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java
index 0f31cc7..63f186b 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/BouncyCastlePQCProvider.java
@@ -22,7 +22,7 @@
     extends Provider
     implements ConfigurableProvider
 {
-    private static String info = "BouncyCastle Post-Quantum Security Provider v1.64";
+    private static String info = "BouncyCastle Post-Quantum Security Provider v1.67";
 
     public static String PROVIDER_NAME = "BCPQC";
 
@@ -37,7 +37,7 @@
     private static final String ALGORITHM_PACKAGE = "org.bouncycastle.pqc.jcajce.provider.";
     private static final String[] ALGORITHMS =
         {
-            "Rainbow", "McEliece", "SPHINCS", "NH", "XMSS", "QTESLA"
+            "Rainbow", "McEliece", "SPHINCS", "LMS", "NH", "XMSS", "QTESLA"
         };
 
     /**
@@ -47,7 +47,7 @@
      */
     public BouncyCastlePQCProvider()
     {
-        super(PROVIDER_NAME, 1.64, info);
+        super(PROVIDER_NAME, 1.67, info);
 
         AccessController.doPrivileged(new PrivilegedAction()
         {
@@ -127,6 +127,11 @@
         }
     }
 
+    public AsymmetricKeyInfoConverter getKeyInfoConverter(ASN1ObjectIdentifier oid)
+    {
+        return (AsymmetricKeyInfoConverter)keyInfoConverters.get(oid);
+    }
+
     public void addAttributes(String key, Map<String, String> attributeMap)
     {
         for (Iterator it = attributeMap.keySet().iterator(); it.hasNext();)
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/LMS.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/LMS.java
new file mode 100644
index 0000000..8579528
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/LMS.java
@@ -0,0 +1,30 @@
+package org.bouncycastle.pqc.jcajce.provider;
+
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
+import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
+
+public class LMS
+{
+    private static final String PREFIX = "org.bouncycastle.pqc.jcajce.provider" + ".lms.";
+
+    public static class Mappings
+        extends AsymmetricAlgorithmProvider
+    {
+        public Mappings()
+        {
+        }
+
+        public void configure(ConfigurableProvider provider)
+        {
+            provider.addAlgorithm("KeyFactory.LMS", PREFIX + "LMSKeyFactorySpi");
+            provider.addAlgorithm("Alg.Alias.KeyFactory." + PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, "LMS");
+
+            provider.addAlgorithm("KeyPairGenerator.LMS", PREFIX + "LMSKeyPairGeneratorSpi");
+            provider.addAlgorithm("Alg.Alias.KeyPairGenerator." + PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, "LMS");
+
+            provider.addAlgorithm("Signature.LMS", PREFIX + "LMSSignatureSpi$generic");
+            provider.addAlgorithm("Alg.Alias.Signature." + PKCSObjectIdentifiers.id_alg_hss_lms_hashsig, "LMS");
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/XMSS.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/XMSS.java
index 2e7bfbb..878f1c1 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/XMSS.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/XMSS.java
@@ -1,6 +1,7 @@
 package org.bouncycastle.pqc.jcajce.provider;
 
 import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
+import org.bouncycastle.asn1.isara.IsaraObjectIdentifiers;
 import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
 import org.bouncycastle.jcajce.provider.util.AsymmetricAlgorithmProvider;
 import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
@@ -23,6 +24,10 @@
             provider.addAlgorithm("KeyFactory.XMSS", PREFIX + "XMSSKeyFactorySpi");
             provider.addAlgorithm("KeyPairGenerator.XMSS", PREFIX + "XMSSKeyPairGeneratorSpi");
 
+            provider.addAlgorithm("Signature.XMSS", PREFIX + "XMSSSignatureSpi$generic");
+            provider.addAlgorithm("Alg.Alias.Signature." + IsaraObjectIdentifiers.id_alg_xmss, "XMSS");
+            provider.addAlgorithm("Alg.Alias.Signature.OID." + IsaraObjectIdentifiers.id_alg_xmss, "XMSS");
+
             addSignatureAlgorithm(provider, "XMSS-SHA256", PREFIX + "XMSSSignatureSpi$withSha256", BCObjectIdentifiers.xmss_SHA256);
             addSignatureAlgorithm(provider, "XMSS-SHAKE128", PREFIX + "XMSSSignatureSpi$withShake128", BCObjectIdentifiers.xmss_SHAKE128);
             addSignatureAlgorithm(provider, "XMSS-SHA512", PREFIX + "XMSSSignatureSpi$withSha512", BCObjectIdentifiers.xmss_SHA512);
@@ -40,6 +45,10 @@
             provider.addAlgorithm("KeyFactory.XMSSMT", PREFIX + "XMSSMTKeyFactorySpi");
             provider.addAlgorithm("KeyPairGenerator.XMSSMT", PREFIX + "XMSSMTKeyPairGeneratorSpi");
 
+            provider.addAlgorithm("Signature.XMSSMT", PREFIX + "XMSSMTSignatureSpi$generic");
+            provider.addAlgorithm("Alg.Alias.Signature." + IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT");
+            provider.addAlgorithm("Alg.Alias.Signature.OID." + IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT");
+
             addSignatureAlgorithm(provider, "XMSSMT-SHA256", PREFIX + "XMSSMTSignatureSpi$withSha256", BCObjectIdentifiers.xmss_mt_SHA256);
             addSignatureAlgorithm(provider, "XMSSMT-SHAKE128", PREFIX + "XMSSMTSignatureSpi$withShake128", BCObjectIdentifiers.xmss_mt_SHAKE128);
             addSignatureAlgorithm(provider, "XMSSMT-SHA512", PREFIX + "XMSSMTSignatureSpi$withSha512", BCObjectIdentifiers.xmss_mt_SHA512);
@@ -55,7 +64,9 @@
             provider.addAlgorithm("Alg.Alias.Signature.SHAKE256WITHXMSSMT", "SHAKE256WITHXMSSMT-SHAKE256");
 
             registerOid(provider, PQCObjectIdentifiers.xmss, "XMSS", new XMSSKeyFactorySpi());
+            registerOid(provider, IsaraObjectIdentifiers.id_alg_xmss, "XMSS", new XMSSKeyFactorySpi());
             registerOid(provider, PQCObjectIdentifiers.xmss_mt, "XMSSMT", new XMSSMTKeyFactorySpi());
+            registerOid(provider, IsaraObjectIdentifiers.id_alg_xmssmt, "XMSSMT", new XMSSMTKeyFactorySpi());
         }
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPrivateKey.java
new file mode 100644
index 0000000..c0df9f8
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPrivateKey.java
@@ -0,0 +1,174 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.PrivateKey;
+
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.pqc.crypto.util.PrivateKeyInfoFactory;
+import org.bouncycastle.pqc.jcajce.interfaces.LMSPrivateKey;
+import org.bouncycastle.util.Arrays;
+
+public class BCLMSPrivateKey
+    implements PrivateKey, LMSPrivateKey
+{
+    private static final long serialVersionUID = 8568701712864512338L;
+
+    private transient LMSKeyParameters keyParams;
+    private transient ASN1Set attributes;
+
+    public BCLMSPrivateKey(
+        LMSKeyParameters keyParams)
+    {
+        this.keyParams = keyParams;
+    }
+
+    public BCLMSPrivateKey(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        init(keyInfo);
+    }
+
+    private void init(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        this.attributes = keyInfo.getAttributes();
+        this.keyParams = (LMSKeyParameters)PrivateKeyFactory.createKey(keyInfo);
+    }
+
+    public long getIndex()
+    {
+        if (getUsagesRemaining() == 0)
+        {
+            throw new IllegalStateException("key exhausted");
+        }
+
+        if (keyParams instanceof LMSPrivateKeyParameters)
+        {
+            return ((LMSPrivateKeyParameters)keyParams).getIndex();
+        }
+        return ((HSSPrivateKeyParameters)keyParams).getIndex();
+    }
+
+    public long getUsagesRemaining()
+    {
+        if (keyParams instanceof LMSPrivateKeyParameters)
+        {
+            return ((LMSPrivateKeyParameters)keyParams).getUsagesRemaining();
+        }
+        return ((HSSPrivateKeyParameters)keyParams).getUsagesRemaining();
+    }
+
+    public LMSPrivateKey extractKeyShard(int usageCount)
+    {
+        if (keyParams instanceof LMSPrivateKeyParameters)
+        {
+            return new BCLMSPrivateKey(((LMSPrivateKeyParameters)keyParams).extractKeyShard(usageCount));
+        }
+        return new BCLMSPrivateKey(((HSSPrivateKeyParameters)keyParams).extractKeyShard(usageCount));
+    }
+
+    public String getAlgorithm()
+    {
+        return "LMS";
+    }
+
+    public String getFormat()
+    {
+        return "PKCS#8";
+    }
+
+    public byte[] getEncoded()
+    {
+        try
+        {
+            PrivateKeyInfo pki = PrivateKeyInfoFactory.createPrivateKeyInfo(keyParams, attributes);
+
+            return pki.getEncoded();
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (o instanceof BCLMSPrivateKey)
+        {
+            BCLMSPrivateKey otherKey = (BCLMSPrivateKey)o;
+
+            try
+            {
+                return Arrays.areEqual(keyParams.getEncoded(), otherKey.keyParams.getEncoded());
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException("unable to perform equals");     // should never happen.
+            }
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        try
+        {
+            return Arrays.hashCode(keyParams.getEncoded());
+        }
+        catch (IOException e)
+        {
+            throw new IllegalStateException("unable to calculate hashCode");     // should never happen.
+        }
+    }
+
+    CipherParameters getKeyParams()
+    {
+        return keyParams;
+    }
+
+    public int getLevels()
+    {
+        if (keyParams instanceof LMSPrivateKeyParameters)
+        {
+            return 1;
+        }
+        else
+        {
+            return ((HSSPrivateKeyParameters)keyParams).getL();
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        init(PrivateKeyInfo.getInstance(enc));
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPublicKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPublicKey.java
new file mode 100644
index 0000000..4b018d7
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/BCLMSPublicKey.java
@@ -0,0 +1,143 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.PublicKey;
+
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
+import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
+import org.bouncycastle.pqc.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.pqc.jcajce.interfaces.LMSKey;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Encodable;
+
+public class BCLMSPublicKey
+    implements PublicKey, LMSKey
+{
+    private static final long serialVersionUID = -5617456225328969766L;
+    
+    private transient LMSKeyParameters keyParams;
+
+    public BCLMSPublicKey(
+        LMSKeyParameters keyParams)
+    {
+        this.keyParams = keyParams;
+    }
+
+    public BCLMSPublicKey(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        init(keyInfo);
+    }
+
+    private void init(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        this.keyParams = (LMSKeyParameters)PublicKeyFactory.createKey(keyInfo);
+    }
+
+    /**
+     * @return name of the algorithm - "LMS"
+     */
+    public final String getAlgorithm()
+    {
+        return "LMS";
+    }
+
+    public byte[] getEncoded()
+    {
+        try
+        {
+            SubjectPublicKeyInfo pki = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(keyParams);
+            return pki.getEncoded();
+        }
+        catch (IOException e)
+        {
+            return null;
+        }
+    }
+
+    public String getFormat()
+    {
+        return "X.509";
+    }
+
+    CipherParameters getKeyParams()
+    {
+        return keyParams;
+    }
+
+    public boolean equals(Object o)
+    {
+        if (o == this)
+        {
+            return true;
+        }
+
+        if (o instanceof BCLMSPublicKey)
+        {
+            BCLMSPublicKey otherKey = (BCLMSPublicKey)o;
+
+            try
+            {
+                return Arrays.areEqual(keyParams.getEncoded(), otherKey.keyParams.getEncoded());
+            }
+            catch (IOException e)
+            {
+                return false;
+            }
+        }
+
+        return false;
+    }
+
+    public int hashCode()
+    {
+        try
+        {
+            return Arrays.hashCode(((Encodable)keyParams).getEncoded());
+        }
+        catch (IOException e)
+        {
+            // should never happen, but...
+            return -1;
+        }
+    }
+    
+    public int getLevels()
+    {
+        if (keyParams instanceof LMSPublicKeyParameters)
+        {
+            return 1;
+        }
+        else
+        {
+            return ((HSSPublicKeyParameters)keyParams).getL();
+        }
+    }
+
+    private void readObject(
+        ObjectInputStream in)
+        throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+
+        byte[] enc = (byte[])in.readObject();
+
+        init(SubjectPublicKeyInfo.getInstance(enc));
+    }
+
+    private void writeObject(
+        ObjectOutputStream out)
+        throws IOException
+    {
+        out.defaultWriteObject();
+
+        out.writeObject(this.getEncoded());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/DigestUtil.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/DigestUtil.java
new file mode 100644
index 0000000..299b0c3
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/DigestUtil.java
@@ -0,0 +1,58 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.Xof;
+import org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec;
+
+class DigestUtil
+{
+    public static byte[] getDigestResult(Digest digest)
+    {
+        byte[] hash = new byte[DigestUtil.getDigestSize(digest)];
+
+        if (digest instanceof Xof)
+        {
+            ((Xof)digest).doFinal(hash, 0, hash.length);
+        }
+        else
+        {
+            digest.doFinal(hash, 0);
+        }
+
+        return hash;
+    }
+
+    public static int getDigestSize(Digest digest)
+    {
+        if (digest instanceof Xof)
+        {
+            return digest.getDigestSize() * 2;
+        }
+
+        return digest.getDigestSize();
+    }
+
+    public static String getXMSSDigestName(ASN1ObjectIdentifier treeDigest)
+    {
+        if (treeDigest.equals(NISTObjectIdentifiers.id_sha256))
+        {
+            return XMSSParameterSpec.SHA256;
+        }
+        if (treeDigest.equals(NISTObjectIdentifiers.id_sha512))
+        {
+            return XMSSParameterSpec.SHA512;
+        }
+        if (treeDigest.equals(NISTObjectIdentifiers.id_shake128))
+        {
+            return XMSSParameterSpec.SHAKE128;
+        }
+        if (treeDigest.equals(NISTObjectIdentifiers.id_shake256))
+        {
+            return XMSSParameterSpec.SHAKE256;
+        }
+
+        throw new IllegalArgumentException("unrecognized digest OID: " + treeDigest);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyFactorySpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyFactorySpi.java
new file mode 100644
index 0000000..21529e7
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyFactorySpi.java
@@ -0,0 +1,116 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter;
+
+public class LMSKeyFactorySpi
+    extends KeyFactorySpi
+    implements AsymmetricKeyInfoConverter
+{
+    public PrivateKey engineGeneratePrivate(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof PKCS8EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to PKCS#8 from the spec
+            byte[] encKey = ((PKCS8EncodedKeySpec)keySpec).getEncoded();
+
+            try
+            {
+                return generatePrivate(PrivateKeyInfo.getInstance(ASN1Primitive.fromByteArray(encKey)));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString(), e);
+            }
+        }
+
+        throw new InvalidKeySpecException("unsupported key specification: "
+            + keySpec.getClass() + ".");
+    }
+
+    public PublicKey engineGeneratePublic(KeySpec keySpec)
+        throws InvalidKeySpecException
+    {
+        if (keySpec instanceof X509EncodedKeySpec)
+        {
+            // get the DER-encoded Key according to X.509 from the spec
+            byte[] encKey = ((X509EncodedKeySpec)keySpec).getEncoded();
+
+            // decode the SubjectPublicKeyInfo data structure to the pki object
+            try
+            {
+                return generatePublic(SubjectPublicKeyInfo.getInstance(encKey));
+            }
+            catch (Exception e)
+            {
+                throw new InvalidKeySpecException(e.toString(), e);
+            }
+        }
+
+        throw new InvalidKeySpecException("unknown key specification: " + keySpec + ".");
+    }
+
+    public final KeySpec engineGetKeySpec(Key key, Class keySpec)
+        throws InvalidKeySpecException
+    {
+        if (key instanceof BCLMSPrivateKey)
+        {
+            if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new PKCS8EncodedKeySpec(key.getEncoded());
+            }
+        }
+        else if (key instanceof BCLMSPublicKey)
+        {
+            if (X509EncodedKeySpec.class.isAssignableFrom(keySpec))
+            {
+                return new X509EncodedKeySpec(key.getEncoded());
+            }
+        }
+        else
+        {
+            throw new InvalidKeySpecException("unsupported key type: "
+                + key.getClass() + ".");
+        }
+
+        throw new InvalidKeySpecException("unknown key specification: "
+            + keySpec + ".");
+    }
+
+    public final Key engineTranslateKey(Key key)
+        throws InvalidKeyException
+    {
+        if (key instanceof BCLMSPrivateKey || key instanceof BCLMSPublicKey)
+        {
+            return key;
+        }
+
+        throw new InvalidKeyException("unsupported key type");
+    }
+
+    public PrivateKey generatePrivate(PrivateKeyInfo keyInfo)
+        throws IOException
+    {
+        return new BCLMSPrivateKey(keyInfo);
+    }
+
+    public PublicKey generatePublic(SubjectPublicKeyInfo keyInfo)
+        throws IOException
+    {
+        return new BCLMSPublicKey(keyInfo);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyPairGeneratorSpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyPairGeneratorSpi.java
new file mode 100644
index 0000000..6caa8ba
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSKeyPairGeneratorSpi.java
@@ -0,0 +1,135 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator;
+import org.bouncycastle.crypto.CryptoServicesRegistrar;
+import org.bouncycastle.crypto.KeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSKeyGenerationParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSKeyPairGenerator;
+import org.bouncycastle.pqc.crypto.lms.HSSPrivateKeyParameters;
+import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
+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.LMSigParameters;
+import org.bouncycastle.pqc.jcajce.spec.LMSHSSKeyGenParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.LMSHSSParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.LMSParameterSpec;
+
+public class LMSKeyPairGeneratorSpi
+    extends java.security.KeyPairGenerator
+{
+    private KeyGenerationParameters param;
+    private ASN1ObjectIdentifier treeDigest;
+    private AsymmetricCipherKeyPairGenerator engine = new LMSKeyPairGenerator();
+
+    private SecureRandom random = CryptoServicesRegistrar.getSecureRandom();
+    private boolean initialised = false;
+
+    public LMSKeyPairGeneratorSpi()
+    {
+        super("LMS");
+    }
+
+    public void initialize(
+        int strength,
+        SecureRandom random)
+    {
+        throw new IllegalArgumentException("use AlgorithmParameterSpec");
+    }
+
+    public void initialize(
+        AlgorithmParameterSpec params,
+        SecureRandom random)
+        throws InvalidAlgorithmParameterException
+    {
+        if (params instanceof LMSKeyGenParameterSpec)
+        {
+            LMSKeyGenParameterSpec lmsParams = (LMSKeyGenParameterSpec)params;
+
+            param = new LMSKeyGenerationParameters(new LMSParameters(lmsParams.getSigParams(), lmsParams.getOtsParams()), random);
+
+            engine = new LMSKeyPairGenerator();
+            engine.init(param);
+        }
+        else if (params instanceof LMSHSSKeyGenParameterSpec)
+        {
+            LMSKeyGenParameterSpec[] lmsParams = ((LMSHSSKeyGenParameterSpec)params).getLMSSpecs();
+            LMSParameters[] hssParams = new LMSParameters[lmsParams.length];
+            for (int i = 0; i != lmsParams.length; i++)
+            {
+                hssParams[i] = new LMSParameters(lmsParams[i].getSigParams(), lmsParams[i].getOtsParams());
+            }
+            param = new HSSKeyGenerationParameters(hssParams, random);
+
+            engine = new HSSKeyPairGenerator();
+            engine.init(param);
+        }
+        else if (params instanceof LMSParameterSpec)
+        {
+            LMSParameterSpec lmsParams = (LMSParameterSpec)params;
+
+            param = new LMSKeyGenerationParameters(new LMSParameters(lmsParams.getSigParams(), lmsParams.getOtsParams()), random);
+
+            engine = new LMSKeyPairGenerator();
+            engine.init(param);
+        }
+        else if (params instanceof LMSHSSParameterSpec)
+        {
+            LMSParameterSpec[] lmsParams = ((LMSHSSParameterSpec)params).getLMSSpecs();
+            LMSParameters[] hssParams = new LMSParameters[lmsParams.length];
+            for (int i = 0; i != lmsParams.length; i++)
+            {
+                hssParams[i] = new LMSParameters(lmsParams[i].getSigParams(), lmsParams[i].getOtsParams());
+            }
+            param = new HSSKeyGenerationParameters(hssParams, random);
+
+            engine = new HSSKeyPairGenerator();
+            engine.init(param);
+        }
+        else
+        {
+            throw new InvalidAlgorithmParameterException("parameter object not a LMSParameterSpec/LMSHSSParameterSpec");
+        }
+
+        initialised = true;
+    }
+
+    public KeyPair generateKeyPair()
+    {
+        if (!initialised)
+        {
+            param = new LMSKeyGenerationParameters(new LMSParameters(LMSigParameters.lms_sha256_n32_h10, LMOtsParameters.sha256_n32_w2), random);
+
+            engine.init(param);
+            initialised = true;
+        }
+
+        AsymmetricCipherKeyPair pair = engine.generateKeyPair();
+
+        if (engine instanceof LMSKeyPairGenerator)
+        {
+            LMSPublicKeyParameters pub = (LMSPublicKeyParameters)pair.getPublic();
+            LMSPrivateKeyParameters priv = (LMSPrivateKeyParameters)pair.getPrivate();
+
+            return new KeyPair(new BCLMSPublicKey(pub), new BCLMSPrivateKey(priv));
+        }
+        else
+        {
+            HSSPublicKeyParameters pub = (HSSPublicKeyParameters)pair.getPublic();
+            HSSPrivateKeyParameters priv = (HSSPrivateKeyParameters)pair.getPrivate();
+
+            return new KeyPair(new BCLMSPublicKey(pub), new BCLMSPrivateKey(priv));
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSSignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSSignatureSpi.java
new file mode 100644
index 0000000..19f5edb
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/lms/LMSSignatureSpi.java
@@ -0,0 +1,182 @@
+package org.bouncycastle.pqc.jcajce.provider.lms;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.crypto.Digest;
+import org.bouncycastle.crypto.digests.NullDigest;
+import org.bouncycastle.pqc.crypto.ExhaustedPrivateKeyException;
+import org.bouncycastle.pqc.crypto.MessageSigner;
+import org.bouncycastle.pqc.crypto.lms.LMSContext;
+import org.bouncycastle.pqc.crypto.lms.LMSContextBasedSigner;
+import org.bouncycastle.pqc.crypto.lms.LMSContextBasedVerifier;
+
+public class LMSSignatureSpi
+    extends Signature
+{
+    protected LMSSignatureSpi(String algorithm)
+    {
+        super(algorithm);
+    }
+
+    private Digest digest;
+    private MessageSigner signer;
+    private SecureRandom random;
+
+    private LMSContextBasedSigner lmOtsSigner;
+    private LMSContextBasedVerifier lmOtsVerifier;
+
+    protected LMSSignatureSpi(String sigName, Digest digest)
+    {
+        super(sigName);
+
+        this.digest = digest;
+    }
+
+    protected void engineInitVerify(PublicKey publicKey)
+        throws InvalidKeyException
+    {
+        if (publicKey instanceof BCLMSPublicKey)
+        {
+            digest = new NullDigest();
+            
+            digest.reset();
+            lmOtsVerifier = (LMSContextBasedVerifier)((BCLMSPublicKey)publicKey).getKeyParams();
+        }
+        else
+        {
+            throw new InvalidKeyException("unknown public key passed to XMSS");
+        }
+    }
+
+    protected void engineInitSign(PrivateKey privateKey, SecureRandom random)
+        throws InvalidKeyException
+    {
+        this.random = random;
+        engineInitSign(privateKey);
+    }
+
+    protected void engineInitSign(PrivateKey privateKey)
+        throws InvalidKeyException
+    {
+        if (privateKey instanceof BCLMSPrivateKey)
+        {
+            lmOtsSigner = (LMSContextBasedSigner)((BCLMSPrivateKey)privateKey).getKeyParams();
+            if (lmOtsSigner.getUsagesRemaining() == 0)
+            {
+                throw new InvalidKeyException("private key exhausted");
+            }
+            digest = null;
+        }
+        else
+        {
+            throw new InvalidKeyException("unknown private key passed to LMS");
+        }
+    }
+
+    protected void engineUpdate(byte b)
+        throws SignatureException
+    {
+        if (digest == null)
+        {
+            digest = getSigner();
+        }
+        digest.update(b);
+    }
+
+    protected void engineUpdate(byte[] b, int off, int len)
+        throws SignatureException
+    {
+        if (digest == null)
+        {
+            digest = getSigner();
+        }
+        digest.update(b, off, len);
+    }
+
+    private Digest getSigner()
+        throws SignatureException
+    {
+        try
+        {
+            return lmOtsSigner.generateLMSContext();
+        }
+        catch (ExhaustedPrivateKeyException e)
+        {
+            throw new SignatureException(e.getMessage(), e);
+        }
+    }
+
+    protected byte[] engineSign()
+        throws SignatureException
+    {
+        if (digest == null)
+        {
+            digest = getSigner();
+        }
+
+        try
+        {
+            byte[] sig = lmOtsSigner.generateSignature((LMSContext)digest);
+
+            digest = null;
+            
+            return sig;
+        }
+        catch (Exception e)
+        {
+            if (e instanceof IllegalStateException)
+            {
+                throw new SignatureException(e.getMessage(), e);
+            }
+            throw new SignatureException(e.toString(), e);
+        }
+    }
+
+    protected boolean engineVerify(byte[] sigBytes)
+        throws SignatureException
+    {
+        LMSContext context = lmOtsVerifier.generateLMSContext(sigBytes);
+
+        byte[] hash = DigestUtil.getDigestResult(digest);
+
+        context.update(hash, 0, hash.length);
+
+        return lmOtsVerifier.verify(context);
+    }
+
+    protected void engineSetParameter(AlgorithmParameterSpec params)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated replaced with #engineSetParameter(java.security.spec.AlgorithmParameterSpec)
+     */
+    protected void engineSetParameter(String param, Object value)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    /**
+     * @deprecated
+     */
+    protected Object engineGetParameter(String param)
+    {
+        throw new UnsupportedOperationException("engineSetParameter unsupported");
+    }
+
+    static public class generic
+        extends LMSSignatureSpi
+    {
+        public generic()
+        {
+            super("LMS", new NullDigest());
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java
index e4ff43f..7cd4369 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/mceliece/McEliecePKCSCipherSpi.java
@@ -66,7 +66,7 @@
         }
         catch (Exception e)
         {
-            e.printStackTrace();
+            throw new IllegalBlockSizeException(e.getMessage());
         }
         return output;
     }
@@ -81,7 +81,7 @@
         }
         catch (Exception e)
         {
-            e.printStackTrace();
+            throw new IllegalBlockSizeException(e.getMessage());
         }
         return output;
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java
index 157508c..6de292f 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPrivateKey.java
@@ -25,7 +25,7 @@
  * </p><p>
  * More detailed information about the private key is to be found in the paper
  * of Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
- * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ * Signature Scheme. ACNS 2005: 164-175 (https://dx.doi.org/10.1007/11496137_12)
  * </p>
  */
 public class BCRainbowPrivateKey
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java
index 316ac0b..7011260 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/rainbow/BCRainbowPublicKey.java
@@ -27,7 +27,7 @@
  * </p><p>
  * More detailed information on the public key is to be found in the paper of
  * Jintai Ding, Dieter Schmidt: Rainbow, a New Multivariable Polynomial
- * Signature Scheme. ACNS 2005: 164-175 (http://dx.doi.org/10.1007/11496137_12)
+ * Signature Scheme. ACNS 2005: 164-175 (https://dx.doi.org/10.1007/11496137_12)
  * </p>
  */
 public class BCRainbowPublicKey
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java
index 95cb5be..78b9be9 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/AllTests.java
@@ -43,6 +43,7 @@
         suite.addTestSuite(XMSSTest.class);
         suite.addTestSuite(XMSSMTTest.class);
         suite.addTestSuite(QTESLATest.class);
+        suite.addTestSuite(LMSTest.class);
 
         return new BCTestSetup(suite);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java
index 7d8ddd9..fd509bc 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/FlexiTest.java
@@ -24,7 +24,10 @@
 
     protected void setUp()
     {
-        Security.addProvider(new BouncyCastlePQCProvider());
+        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
+        {
+            Security.addProvider(new BouncyCastlePQCProvider());
+        }
         // initialize sources of randomness
         rand = new Random();
         sr = new SecureRandom();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java
index 424acaa..3047b03 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyPairGeneratorTest.java
@@ -19,7 +19,7 @@
     protected final void performKeyPairEncodingTest(KeyPair keyPair)
     {
         try
-        {;
+        {
             PublicKey pubKey = keyPair.getPublic();
             PrivateKey privKey = keyPair.getPrivate();
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java
index 1c1762e..585d28e 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java
@@ -216,20 +216,15 @@
 
         certGen.setExtensions(extGenerator.generate());
 
-        Signature sig = Signature.getInstance(sigName, BouncyCastlePQCProvider.PROVIDER_NAME);
-
-        sig.initSign(keyPair.getPrivate());
-
-        sig.update(certGen.generateTBSCertificate().getEncoded(ASN1Encoding.DER));
-
         TBSCertificate tbsCert = certGen.generateTBSCertificate();
 
+        Signature sig = Signature.getInstance(sigName, BouncyCastlePQCProvider.PROVIDER_NAME);
+        sig.initSign(keyPair.getPrivate());
+        sig.update(tbsCert.getEncoded(ASN1Encoding.DER));
+
         ASN1EncodableVector v = new ASN1EncodableVector();
-        // TBS
         v.add(tbsCert);
-        // Algorithm Identifier
         v.add((AlgorithmIdentifier)algIds.get(sigName));
-        // Signature
         v.add(new DERBitString(sig.sign()));
 
         return (X509Certificate)CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME)
@@ -255,16 +250,13 @@
 
         certGen.setExtensions(extensions);
 
-        Signature sig = Signature.getInstance(sigName, BouncyCastlePQCProvider.PROVIDER_NAME);
-
-        sig.initSign(signerKey);
-
-        sig.update(certGen.generateTBSCertificate().getEncoded(ASN1Encoding.DER));
-
         TBSCertificate tbsCert = certGen.generateTBSCertificate();
 
-        ASN1EncodableVector v = new ASN1EncodableVector();
+        Signature sig = Signature.getInstance(sigName, BouncyCastlePQCProvider.PROVIDER_NAME);
+        sig.initSign(signerKey);
+        sig.update(tbsCert.getEncoded(ASN1Encoding.DER));
 
+        ASN1EncodableVector v = new ASN1EncodableVector();
         v.add(tbsCert);
         v.add((AlgorithmIdentifier)algIds.get(sigName));
         v.add(new DERBitString(sig.sign()));
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/LMSTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/LMSTest.java
new file mode 100644
index 0000000..42215ea
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/LMSTest.java
@@ -0,0 +1,257 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import junit.framework.TestCase;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
+import org.bouncycastle.pqc.jcajce.interfaces.LMSPrivateKey;
+import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
+import org.bouncycastle.pqc.jcajce.spec.LMSHSSKeyGenParameterSpec;
+import org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Strings;
+
+public class LMSTest
+    extends TestCase
+{
+    public void setUp()
+    {
+        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
+        {
+            Security.addProvider(new BouncyCastlePQCProvider());
+        }
+    }
+
+    public void testKeyPairGenerators()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        trySigning(kp);
+
+        kpGen.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        kp = kpGen.generateKeyPair();
+
+        trySigning(kp);
+
+        kpGen.initialize(new LMSHSSKeyGenParameterSpec(
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1),
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1)
+            ), new SecureRandom());
+
+        kp = kpGen.generateKeyPair();
+
+        trySigning(kp);
+    }
+
+    private void trySigning(KeyPair keyPair)
+        throws Exception
+    {
+        byte[] msg = Strings.toByteArray("Hello, world!");
+        Signature signer = Signature.getInstance("LMS", "BCPQC");
+
+        signer.initSign(keyPair.getPrivate(), new SecureRandom());
+
+        signer.update(msg);
+
+        byte[] sig = signer.sign();
+
+        signer.initVerify(keyPair.getPublic());
+
+        signer.update(msg);
+
+        assertTrue(signer.verify(sig));
+    }
+
+    public void testKeyFactoryLMSKey()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpGen.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(kp.getPublic().getEncoded());
+
+        KeyFactory kFact = KeyFactory.getInstance("LMS", "BCPQC");
+
+        PublicKey pub1 = kFact.generatePublic(x509KeySpec);
+
+        assertEquals(kp.getPublic(), pub1);
+
+        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded());
+
+        PrivateKey priv1 = kFact.generatePrivate(pkcs8KeySpec);
+
+        assertEquals(kp.getPrivate(), priv1);
+
+        kFact = KeyFactory.getInstance(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig.getId(), "BCPQC");
+
+        pub1 = kFact.generatePublic(x509KeySpec);
+
+        assertEquals(kp.getPublic(), pub1);
+    }
+
+    public void testPublicKeyEncodingLength()
+        throws Exception
+    {
+        KeyPairGenerator kpGen1 = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpGen1.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
+
+        KeyPair kp1 = kpGen1.generateKeyPair();
+
+        KeyPairGenerator kpGen2 = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpGen2.initialize(new LMSHSSKeyGenParameterSpec(
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1),
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1)
+            ), new SecureRandom());
+
+        KeyPair kp2 = kpGen2.generateKeyPair();
+
+        assertEquals(kp1.getPublic().getEncoded().length, kp2.getPublic().getEncoded().length);
+    }
+
+    public void testKeyFactoryHSSKey()
+        throws Exception
+    {
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpGen.initialize(new LMSHSSKeyGenParameterSpec(
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1),
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1)
+            ), new SecureRandom());
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(kp.getPublic().getEncoded());
+
+        KeyFactory kFact = KeyFactory.getInstance("LMS", "BCPQC");
+
+        PublicKey pub1 = kFact.generatePublic(x509KeySpec);
+
+        assertEquals(kp.getPublic(), pub1);
+
+        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded());
+        
+        PrivateKey priv1 = kFact.generatePrivate(pkcs8KeySpec);
+
+        assertEquals(kp.getPrivate(), priv1);
+
+        kFact = KeyFactory.getInstance(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig.getId(), "BCPQC");
+
+        pub1 = kFact.generatePublic(x509KeySpec);
+
+        assertEquals(kp.getPublic(), pub1);
+    }
+
+    public void testKeyGenAndSignTwoSigsWithShardHSS()
+        throws Exception
+    {
+        byte[] msg1 = Strings.toByteArray("Hello, world!");
+        byte[] msg2 = Strings.toByteArray("Now is the time");
+
+        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("LMS", "BCPQC");
+
+        kpGen.initialize(
+            new LMSHSSKeyGenParameterSpec(
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4),
+                new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w4)), new SecureRandom());
+
+        KeyPair kp = kpGen.generateKeyPair();
+
+        LMSPrivateKey privKey = ((LMSPrivateKey)kp.getPrivate()).extractKeyShard(2);
+
+        assertEquals(2,  ((LMSPrivateKey)kp.getPrivate()).getIndex());
+
+        assertEquals(2, privKey.getUsagesRemaining());
+        assertEquals(0, privKey.getIndex());
+
+        Signature signer = Signature.getInstance("LMS", "BCPQC");
+
+        signer.initSign(privKey);
+
+        signer.update(msg1);
+
+        byte[] sig1 = signer.sign();
+
+        assertEquals(1, privKey.getIndex());
+
+        signer.initVerify(kp.getPublic());
+
+        signer.update(msg1);
+
+        assertTrue(signer.verify(sig1));
+
+        signer.initSign(privKey);
+
+        signer.update(msg2);
+
+        byte[] sig2 = signer.sign();
+
+        assertEquals(0, privKey.getUsagesRemaining());
+
+        try
+        {
+            signer.update(msg2);
+
+            fail("no exception");
+        }
+        catch (SignatureException e)
+        {
+            assertEquals("hss private key shard is exhausted", e.getMessage());
+        }
+
+        signer = Signature.getInstance("LMS", "BCPQC");
+
+        signer.initVerify(kp.getPublic());
+
+        signer.update(msg2);
+
+        assertTrue(signer.verify(sig2));
+  
+        try
+        {
+            signer.initSign(privKey);
+            fail("no exception");
+        }
+        catch (InvalidKeyException e)
+        {
+            assertEquals("private key exhausted", e.getMessage());
+        }
+
+        assertEquals(2,  ((LMSPrivateKey)kp.getPrivate()).getIndex());
+
+        signer.initSign(kp.getPrivate());
+
+        signer.update(msg1);
+
+        byte[] sig = signer.sign();
+        
+        signer.initVerify(kp.getPublic());
+
+        signer.update(msg1);
+
+        assertTrue(signer.verify(sig));
+        assertFalse(Arrays.areEqual(sig1, sig));
+        assertEquals(3,  ((LMSPrivateKey)kp.getPrivate()).getIndex());
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/PQCSigUtils.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/PQCSigUtils.java
new file mode 100644
index 0000000..c7bb7cc
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/PQCSigUtils.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.pqc.jcajce.provider.test;
+
+import org.bouncycastle.util.Arrays;
+
+public class PQCSigUtils
+{
+    static class SigWrapper
+    {
+        private final byte[] sig;
+
+        SigWrapper(byte[] sig)
+        {
+            this.sig = sig;
+        }
+
+        public boolean equals(Object o)
+        {
+            if (o instanceof SigWrapper)
+            {
+                SigWrapper other = (SigWrapper)o;
+
+                return Arrays.areEqual(other.sig, this.sig);
+            }
+
+            return false;
+        }
+
+        public int hashCode()
+        {
+            return Arrays.hashCode(this.sig);
+        }
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/QTESLATest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/QTESLATest.java
index 453df94..1173fc9 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/QTESLATest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/QTESLATest.java
@@ -10,12 +10,10 @@
 import java.security.PrivateKey;
 import java.security.PublicKey;
 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.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
@@ -23,25 +21,16 @@
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.pqc.asn1.PQCObjectIdentifiers;
-import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
 import org.bouncycastle.pqc.jcajce.spec.QTESLAParameterSpec;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.encoders.Hex;
 import org.bouncycastle.util.test.FixedSecureRandom;
 
 public class QTESLATest
-    extends TestCase
+    extends KeyPairGeneratorTest
 {
     static SecureRandom secureRandom = new SecureRandom();
 
-    public void setUp()
-    {
-        if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
-        {
-            Security.addProvider(new BouncyCastlePQCProvider());
-        }
-    }
-    
     private void doTestSig(KeyPair kp)
         throws Exception
     {
@@ -96,50 +85,6 @@
         return oIn.readObject();
     }
 
-    public void testGenerateKeyPairSigningVerifyingI()
-        throws Exception
-    {
-        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("qTESLA", "BCPQC");
-
-        kpGen.initialize(new QTESLAParameterSpec(QTESLAParameterSpec.HEURISTIC_I), secureRandom);
-
-        KeyPair kp = kpGen.generateKeyPair();
-
-        assertEquals(BCObjectIdentifiers.qTESLA_I, SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()).getAlgorithm().getAlgorithm());
-
-        doTestSig(kp);
-        doTestKey(kp);
-    }
-
-    public void testGenerateKeyPairSigningVerifyingIIISize()
-        throws Exception
-    {
-        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("qTESLA", "BCPQC");
-
-        kpGen.initialize(new QTESLAParameterSpec(QTESLAParameterSpec.HEURISTIC_III_SIZE), secureRandom);
-
-        KeyPair kp = kpGen.generateKeyPair();
-
-        assertEquals(BCObjectIdentifiers.qTESLA_III_size, SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()).getAlgorithm().getAlgorithm());
-
-        doTestSig(kp);
-        doTestKey(kp);
-    }
-
-    public void testGenerateKeyPairSigningVerifyingIIISpeed()
-        throws Exception
-    {
-        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("qTESLA", "BCPQC");
-
-        kpGen.initialize(new QTESLAParameterSpec(QTESLAParameterSpec.HEURISTIC_III_SPEED), secureRandom);
-
-        KeyPair kp = kpGen.generateKeyPair();
-
-        assertEquals(BCObjectIdentifiers.qTESLA_III_speed, SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()).getAlgorithm().getAlgorithm());
-
-        doTestSig(kp);
-        doTestKey(kp);
-    }
 
     public void testGenerateKeyPairSigningVerifyingPI()
         throws Exception
@@ -150,6 +95,10 @@
 
         KeyPair kp = kpGen.generateKeyPair();
 
+        kf = KeyFactory.getInstance("qTESLA");
+
+        performKeyPairEncodingTest(kp);
+
         assertEquals(BCObjectIdentifiers.qTESLA_p_I, SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()).getAlgorithm().getAlgorithm());
 
         doTestSig(kp);
@@ -159,12 +108,17 @@
     public void testGenerateKeyPairSigningVerifyingPIII()
         throws Exception
     {
+
         KeyPairGenerator kpGen = KeyPairGenerator.getInstance("qTESLA", "BCPQC");
 
         kpGen.initialize(new QTESLAParameterSpec(QTESLAParameterSpec.PROVABLY_SECURE_III), secureRandom);
 
         KeyPair kp = kpGen.generateKeyPair();
 
+        kf = KeyFactory.getInstance("qTESLA");
+
+        performKeyPairEncodingTest(kp);
+
         assertEquals(BCObjectIdentifiers.qTESLA_p_III, SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded()).getAlgorithm().getAlgorithm());
 
         doTestSig(kp);
@@ -176,12 +130,12 @@
     {
         KeyFactory keyFact = KeyFactory.getInstance("qTESLA", "BCPQC");
 
-        PublicKey qPub  = keyFact.generatePublic(new X509EncodedKeySpec(new SubjectPublicKeyInfo(new AlgorithmIdentifier(alg), pubKey).getEncoded()));
+        PublicKey qPub = keyFact.generatePublic(new X509EncodedKeySpec(new SubjectPublicKeyInfo(new AlgorithmIdentifier(alg), pubKey).getEncoded()));
         PrivateKey qPriv = keyFact.generatePrivate(new PKCS8EncodedKeySpec(new PrivateKeyInfo(new AlgorithmIdentifier(alg), new DEROctetString(privKey)).getEncoded()));
 
         Signature signer = Signature.getInstance("qTESLA", "BCPQC");
 
-        signer.initSign(qPriv, QTESLASecureRandomFactory.getFixed(seed,256));
+        signer.initSign(qPriv, QTESLASecureRandomFactory.getFixed(seed, 256));
 
         signer.update(msg);
 
@@ -198,71 +152,6 @@
         assertTrue(signer.verify(sig));
     }
 
-    /**
-     * # qTesla-I
-     */
-    public void testCatIVector0()
-        throws Exception
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-
-        byte[] publicKey = Hex.decode("D2F21F7B398701A369A10AB5CA5752324D01D2E4E85D7ADE9C21F2F41CAFDF25B15F173504C315EA250E1EFC243557E90C23509E7D2AE486518448EF18BA837DDE8DD6A30271E793D50DC7999485AE30A649636257E71DE6D65C9803A8FBB384181C6C28604C2B201C938A5198C01704B0F56ACEFD0CF58BC98F81E20CD233C6B5523C8A00F5DB1C934D2B4FB3609C0CB37543A42C4145CAA283C269B49C8EF323CE941F5FCEDB172CA9B5DD116DD6B2B6284DE55C2CC033426C84BD91D74837C6140E12D0C6B05765FD269BD23200FBE110D61856F8C5CE55FDF7269D6BE7D9FC213C885D74F2311A46BD5E32C06308C7C2E7F26BDDBDD10DD1F6DA004B8D28990D9A62276C4FFA90BF6D734DEAF116000CE004EAA0BB25640F9342E9C2FD40774DE360FA1560D478985BE9639F1E5BDD5B3000B364E12AEF3AAFECA99E4DD24530BB223663F095E3254E42A9E0D88639A946388331A4123CCB71A4D8925DF3EF6B6EADD257788B92E8C5E6596AEB77E0FBB86BCD03D0EE68D9B5C08E0E4B67273CCAA8544871276C9375AA0D0CE5681EBA576EF02A617879E2C27E137C8EC106B544D727CEC5B93F67CD4CB2016640B8FCE631243EB08FAD4F1237F3039376061B1AC3FE724C939CD00000EFC693C6D94828428E4CF1103B6BA8139724322ACC8832119F16528056E7D8CCCCFFE683E91AD84BBDC4D492E0215C45B5B75D1C01114DF952C731DB41D3673D80C21E901D06E898E4E6CC2CF3686ADB6760DBD0076ACD31F610C16B291EA1FB595B912ECC5F3E57CDE588CC14046D1D5AF625B0D52D61075082EF052F5F00B66C429E2B2977665E8719F4E4BE4D0EDFF5E6A793DEE09AC0589A6FECF7732684226E03A9520A472DE129A7EE74956FBA661610804F22C235BF5EA9B83D05C634215473309AC5E0BBCC30929AF45F6669340216DD0DCA2E04FA1697546EC1C93BE569068ADC39790435DB82DB3F3C21F16015CF17D1BAB83DACE254A37BC6E4871ED6031B9BA44EF7A71C1E5A709255A9CD226C0DE6733F1615403820C4B3DB50AAB082C04D0D922550DF4A940C9F20710D7B8208E0E97648F02B330EF436F436E011338523718D9DCFA2C6459DB1920A38E978D57D67D8D163313093C58E5A55F96D0DD7A170F3F255CEC4CCAAFA5044D1B21B2B5D44EA76CFC8F5B1A03A99D5DFD04A0ACE897CF480A008819FDBB7216313829CD14A9A1C9596F95ABB48D6F88AFB2B852F2E2CE687B0623D52A81001589EC05D7F6E582B79F2D036030BC6D1573618A83860A8A77C144D7B2DB988F84B16AF61E0931C27478C99B9A1C15801D0A01464DA0611318334238B2745653F14690E37699BAF5A7576FE451F5EAD46DEF2694711E018E37B24EDA8EBB9364553C5EB976DF38D9E4D21C2D174153ACBB0E2644C9C1EC56C3DB51514AED57AE8653C8361262CC21EFD6CF410160F8070753C59439465E62EAF8A34C17E8853C9817F327E273C2D2911C77C1BF9090D30C4243A39F9865545A83D089C5CB23880450E18A4B0036531164B004072E2EDCDF1A015323256BADBE8C1548F2A4CC00241B18825187015D322CE160BA48C4FAF070969A6F6CA9495E6A1959F0923394E7E5E4820B7C6358C61F4471128C67ACA4900728452BD64F6598DB9A421C8D4D33CA8C077C468DDF8FE8F387E950ED979EC2A1AF8FC473529EA069C21A9F26AA5811D343B0E3373183DB3CA9F10E63BA3FC7F81F4999BD0FBD0CA5D6C5546E9B7ABE2AC4D7A5C0FDC8C33A257A8F09281DE4C38D1273B5B37C88F8611CACD58C4E7F9BB916169104CCFA6FB7087FDA4150D5B84F4837C0DB5C6DB321FC7DC79FD90F707456330B37E0109F0ADE25433D3637112E2D96901E22C734407BD988AD203CA805AE8F757EEA09F327DD49710B471CFC197724948A011E597F3A4564CE00FF701B9B240347F227F2BE01582E07680AC993699D2E1536A155B96AE4E461E3D019F0350CBC52EC12186069382FDBD19CE7D70734FD72F8E61361D6BF9EEDEFEA6D44B6B50E1612ADA4E42033329B098318DD9CF695A2921A332204044994F244C0944993B08009265B8004398CF119F95FCC217D38228F1D1F14BCFC5B7160986C339");
-        byte[] secretKey = Hex.decode("2074F000FEEE3B804206EEEB4FFE07FBEF2FC006FB839F4101076400BFFA09AC6F00FE2228C080FE0944CF7FFEE16BD0FEF80D20A0BFF912102080FBEDF7BF0000FE5F9FBD05CCBF2F3FFFDEAB3F00001F48E000032A648F8104F5EBBFBFFFF92FB0400DF78380C1FAF53FD07FFEDC27C03D00D3FB5F8002F153C080030FD88FFEFA139C5F410CFF5FF000081F38D07D01E137A08002120050FF08D4EF1F42010BBC6FFFFE0EE4AF4103D23F0042FE061C60C2F8F5BF8FFDF9111C604200270010BDF8025CB04107F387C07F06EC0770C201FC53003FF60B5070C1011BE0BF800518485000F9F8FF9F81061E6410BCFCE42F60BFFAF4DF5F80FA020450C0EEB0DBFFBDF9DFDBFF8100F42F00FF091068A04101F2DFCF41F90B28B07FF10FCC2F80FFE73BD03FF9F4F35F3F07FF6BC0C2031AECCF7D04F467E03FF2EDDBDFFF0C02804F3F0608D02F3F060C28407DFD2F54AFFFF905D02FBEFCFB8340FE040C885F8103EA1FA001F8F2F36FBCFF1FDC2F42012D1020C0FEF8DBAFC1061E2CB00103F7CF20BE032E5810420417AC1FBEF3F537F0FC05F807603E02FDFFBF7DF2EA13A07FFDF84F40FE070908A0C1FBE523A0810A04703080FEFEDFFF3D01FB4730020A033440BE0AF86F8F42F6E99BA0C1FFF5BF1001F9F15FC0FEFE06E4AF81FDFA4F90FEFC27B02FBFF702DCEF00F7FB83807F04F0CB3F80FDE09F8F3F01E553A0C006016CA0FFF438983F3E05F5D7DF3FF71A10F03E030BBC8F7FFB19C84F81FCFF03D0BEF9EEDBF0BE02E9C71F3E05201830FF03EC17C0800028741F0000D97B60C004F34F60FE0206788042FD04CCDF00081A8CAF800401708F7F10EE0B70C1F411D0AF00011CDCEF3EFA1654C081FCDC7F907FFB0104508208FDAFBFC0F815B0307FFED417B0C0FFF0471081FFD8ABFF8205134C90FD03EC4FA07FFF1434F08003FE9B507F01DD932F3FF6DC833F81FAF6EBCFC1FC0274F07C0201F4DF3EFB203850FEFF0BE4EF4008F71B20FF04E3FBDF7E030A608FC0FF1904E0BFF5FC0B50BFFF18AC3F3FFA08C8BFFF04EED31F41FD0B78CFBE030B38507F0302DC4FBE00E85B300000ECD78F410105C02FFFFAF393CF8102EF278040F20A288080FF0FD03F7FFCFCF78F8009104070FEFDDF2F80FFFD1DE4CFC0FEEE57D03EFA06E48F400103E89F010228DCFF7F07FC9F3F41FEF4235040F309A4E03F041D980FC1FFF85BD07E00C88BDF0008F5EB7FFEF7DFDB9FBFF7F43B307EF60B688042052B982F83040AE89FBEF2E51BF0C00621CC0FC40817BCF0FD00F3D3EF00FBE75B2001F8170C3041FBFEBFBFFEFDE487C0FEFD318C3FC0FEEE6B8F80FAF4834000F8FDF37FC0051A0C00C200FCB78F0000F2DB1F42F812649F3FF9266CD0C1FC0B8CB0C2F8E5B7CFC0F6062440FE0304F8EF4009145CE03FF5F73350BFF80C9480BEFAEAFB2F02FEF9CF6040FC0E6C403F09016040BE0109A0403F01FFDFCFFE072C48CF00F8FFBFCFBFF80AA04F0103E6A3FF40F706ACEFC0032AE09FBF0C0948CF3E012914303EFD1518904106C917907EF9F56BC000FF1650D0C0FF008CE07F050F28913D0126B44F4205FAC3AFC0F40134D07D0CE93BB03E04EC735FC202035C1F40FE0810A08103E8DF0FBDFD22D8AFFE080A5050C10007186000FE06DC5FBDFA0DC8EF020300EC6F3F071248B080FEF9D71F800B0D3C208008F9975F41F902BCAF01FDF06F9000FC17A0B0C1FA17A42F41F8FABBB03D090108707FFCF5BF2FC107ECB390FE05E25FD03DFF11706040FDEFE34F80F62F30E0FF060E3060FFFC993B08009265B8004398CF119F95FCC217D38228F1D1F14BCFC5B7160986C339F23EB15423271EF1CF476289657DBBB1460665D3944B78BEE92D15AA609768F9");
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] sm = Hex.decode("AF8718AB747CEFA75F4A5F26B5392113BDE8CAF1DDDF1D45B473CB64DFFC255B2E211588DAB5D5D2C5407EBBB684E7D8F0C6AB8EB3AB874CEEB87C9CE32568D0ADA6A3B9716EF9AB92BB9F02424D91DD5C3768E4598D2DA954D0664EA3D61070FA4304983B44D6098FB74B55FE40F156F07D0E39F11DBDE5E0549854ACBD65B2F6EFDBF3CEFB27DB44FA18EAF9BDA9F4409FE020B8FB0C7E9A1C17F5A1D59D6996FC1EC43AE246AAE1A8FC777EF5912EEC95A6F9A2B32A91C2EEBD733E0EAA43C3A33E989D1DEF0B1F412C6299628DF5AEEB599391C411942873FDCE0FA701C58FD2FB383A7FEF455AE12F415496BB0F6E75441BB0F670910F207CFC372B823A099EA84105E1B316CA33587FE990017BA0C52C1EA85C9E0F030B63168B093EE83D81C47EE70D5EEE3F5603E00C3FD306DF8D58C594B4E2047397FAE4BB83FCAC0D3A8BF7896770017860E9612BC4FE5E416AD961C277836C89E29B22C9C6A1F2F8A55FD84D57F4C3550EC14E9B30BFD18A3E479217DB48B65B9E3ACF731C08F8655D2D2F689EB6547C5241887CF017FF9F0EFC49D3448D623353C6846F2C547090873903DC281D2D327C1DD8B49BF87CDF28DDAB0BE1D48B8874D8693DACFEE07E51D5137B57F58D1EDFA374C5FC2A9C42FCA63393C8F24160BF54110D8721BADFA288F304E345FBDF179AC43FE45DFE55D98683A8B7E0A7B48BA4193B01294D6666688EA4099E842F78402874FB1CE5A52FB14B6C6BA3343941E0012556C1BC8184DB46B1E49161E5504815ADB45987E7EABC1C5657D5D8F23847B728F5AD51290995BB742EFB99528147D423FB56EE1118E4C170523E4CE2734219902FB54457BDC111F54497002C0D06D4EB11FED96683DD3F5E1A938F4D141DB638BEC5C8A5BBD5E7F4BDCFB8D03C4D003BC0DEA33119015FC6D2468C0A5635049A04055B542463B4AC17FF4AEB339E674A1E5EEE7C0FACB7E6CE7D2B990C4E5EAA29030F5CB76BD83CB59D215875439F68BD8731A4D5F54B1D27B905AC42D381AC8A1C6BF1F785CCAC2CCA0D08411A2845A920DBC51C82105205F1772DBB0914030BF06115CAC3596E0BE6F97ABBEEB6FC10A84819C84507348F2C887366A82B6FA5DB6B34111066DA4A41A709AA7EE8622A3B3AD5C854381440D08C49BBAA025E7F1D459A2606368285B12E50330DA0FDFB9B50D34CC3664B9F13A4B9E0E65F7F5390A4D8D25353CA2DDC5BFF4A1238321AFE33BBA3318D5984AE57799ACBAB7B5A0C52120CA6014B89B3BEE993183703BC717871A1D080E57ED5E4FB5C64AA4620B5F78DD4144C1005ED3D4D2F1E94FF01DBDF0107B52D5FD3B212A72AAE32BD32846E0B31AEB2064A30361E8F58A82937AAE4C3E74A61C9953406B946809B11379411C910A9E9B7E2384D0E9C09A78763CB2DFF356941B2D863C35FCF0D5735B478D62D1D90DC21E833C871A77E533EACDB08081105BA3B65E8953EFECB9C2096DEEEEDACAD630133A6D35AAADF07019EEBE7D1C9EA1CD25F55454D861C1A4A17BDE58FAEE5696FA4DD496A4C0AE443524BB33B98D8F254ACB9CCB852CA4561BB4E3830A24E5EDB9233A20B37F42B323DFA6DC1A891F0C306A121EA94AD8071D107C4B4CDB294FD524014339CBB821F60CE298EABABFD3D64F6F5237DBC14BA7867740F047325691DEC988E401E8CF1427CD040AFA8A33B8BFFC2AC080ECFD7BD4F22646CC7013210B80A99B1F7D6031FE4F50768531C604E7AE9349E87A0006566B61EFE18DF34C385B1B5E8D35946D8E53A75A7412DEC7E8A28A06E8DA2BA3B860AF01455359C01EEDBFB96D6881E5EEAEA7E1B2663A18410218B1588DE897E8E01DFCD72D08757FE1CAEAEDF71F7303CB91E91EE7ED7C7BC3BA6F72A3C8382FC3ED355C6F56CDC8630B2C3A06EA9DF197823448E70C01F349FB0E6D69958AF7347D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-
-        doTestKAT(PQCObjectIdentifiers.qTESLA_I, publicKey, secretKey, seed, msg, sm);
-    }
-
-    /**
-     * # qTesla-I
-     */
-    public void testCatIVector1()
-        throws Exception
-    {
-        byte[] seed = Hex.decode("64335BF29E5DE62842C941766BA129B0643B5E7121CA26CFC190EC7DC3543830557FDD5C03CF123A456D48EFEA43C868");
-
-        byte[] publicKey = Hex.decode("6B8495B1C393966CE20EE722515892480FDDA8F7848245573308D4F1C3C93C4F31B262002E32BCBFBCE58F58C95322F9158CB10F30ED2DE4E1F202DDE2C4F68143F354A52F2C27B12B8E434E14EABC9C61B421708B809CDB3908B75106A11B09AC4EFC19662DDC818D4D2A50827D6197B8405C3C9D1AED8A513D3BED08CC60BD04B028D50CD55D9ECD3640EF81047F505B3705AFBDE294DF42EE9E488D647E9C7D532724ECF6501B18680F4376EEE1E0ED1CB4DAF5861F19AFFCA2092C8A6845224008B437F352695B7827E55C7F0BE5A49B464B4654280FE67A137F69924F8529FD947C5B1E9953BC86808243FECE6E03682FE46396D5B174148059703694A74D9DD399B64714EFD666874AE771E1C8A7C2E73D03EBB82731DE05324BFDC8356D5809A40835393B78CF577778B6DA061B2E640A083E5748A6D88CA3A1B106BE566B86C3293886CAF38BA93E16267BE96A0E91E08FD8683406779C1528298FC98101B1ABE625200B1296C50170388B5A5432A020E2D6DD7B40524B52FB960B8FF3FDDE3526842D355AB860B78F9A4AAE588F548F7EA05F94ED711646D82D4BBF8A22244FD9C466DD71E4C314B98235D819321052039F5996C216DA9A1B0BC897A1DA04F2DB48944CAA0C0F5BDCDC10498FD1E3B6A76EC8158ADB9280F7C0F43E1E835C178B114C939F94DE20974301D9AFB3D5D4CD9D3C24152F891E8019BBDF5B74E7212B85E5C6C90614219F54E674334D1D3A57EE5889F2E6C334D2475FF899FC50DA49C0B106F05810868F850A95411E6B616F56397221E45A363A2A4066AFB5A56CCFCFC3419F99B5CF87C977B6AD926504062CCEAF1EFB578EE1A2EC6CD6D1244EB20604E94D37C283238105B0F4834FB2946B8523F308D0020B1C8557403EF85235CF9F547842D6D94A8DD380BB06732EFF51FD315CF111BE138A7D118643B5A8921FE03EF309169539D7779CBA09C6079BCBBDCF84C383939E55C9F042FE5130E4E31EA33E2412A9F3B308F781E8353F7261DE83B20A5DD66210F05B71B49BFB499D60D8C67E2CC72B67D25E6B550BAF745F0E11F29A185406752B0109EEE7CB503B6F785DBEBA42270E9AAE1991FA41B5758A9D0CD74C6823EF23D4D62ECE67370E0732C43A1D54DC61F54CF507C251758E6C78D6DAA5120E202CAFEBD58A25A933B4B48E1A78ECF36104467A7167231918E93AC356C4AE2FCDF284A3B0E9F19038F8985204BC35839804484D3D4194E1E72A7B22FBBFE03F00072A3C05689FBC6E11C71DE2748826A40261A6357904B76ABA722AB91DE9B3C9FC66034B2C06AE45625E8071552A80A807F20A3ABD1D094512C64B57212941D3190638CE3FE69E5CC8759FEBEDDFA46FCE02B822DD32F1751BC41F4EDCDA63BE023154FE075275A28548450494D9743AC52DAB26971C78371AAA3D87B8F000E95FD79EA6F17B5F614FD0626C236F87072C56D8F7F7EEC167323F79A26E32048BF7C43C00904697F1E4C93D018D7F0845EACC01D3989D3502E2891B776B11CB349AF9C462E83AB1039B30688E153D7C1AA62C46B8889E404936912BCCA2517C059B89EFFC1FE1488C2EE3473C27805715ADE619BD3645D540C84D187441A46C0D904C3C9E2A92A92FCDD6D86380395A934DB93979AEA13C428EAFD9259CB3E2478DB643D0D6913AC275ACD2387808669D2FC40FC6011A87AA1F03639D02692BB18322D6417808A902A403068A7A45DC62A5ADBEC18458AD4F91BEBF7FF60494AA7C0A02AA6E067A86430CAAA3536056EE44944CBA38B7AE9016D66621703A77BCD36BD104BC2A040A12577DBA2FBF4B89AE4F49D202C27681B05BAA8C4026E0A60D44140A2213CAF78BEE9047A10569ABE5C568FBEBEA276DFDCF905F8398432129A82AF45CBD10BE668D0C60F4B64327388B830B1EDFE4ADB67EC5472541AD29C16B732A5F2F6B8EB1DA671B6F1ACC4AB510694F2AAE8055127734591BCEDC1077B757C036ECA8E4A114A649EA4730EAC4309B07DF98A5D8EA838C84675A7D10EC93E1D36704B23BD04E27F635AC6DA31959BEA5A10CA37B7193664419926D965E7F1916EC44B52B4BA993C5FBE705800B2C070B09CF36E0908FB89F71E2F7AD9448");
-        byte[] secretKey = Hex.decode("E2FB4F00FFFF37F07F1016B87FFE00DAD38F3D08E9BFBF7F040634B0C2FF340490FF0205F87F8106F3075080FE0F105080FEE9679F0006FA4720C110F4EBFF7FFD13840080FFE60B80BC04003C307F00F827807F05EFD77F800217CC5FFDFF0AC0B0BE0500701F80F4D48B9FFEFF184C70FF00E9DF7F4110180810FEFFE1F31F3E03F79B3F3E00FAEBBF7E0508D0AF80F52BF0807EFFEC3F203EF61CC0F07E04EEE30FC1FF02E42FBF00E65300000A2674E03FFD1460607F0714F08F3E050E9CF07E03FB7B7F7EFBF4B76F3FFCFF6B303DFB0D981F0202E56F60C000E37BB08106FFFB1F80020808F0BFF6CD43D081032354A040F8CE63C081FC070820800401E8CF7DFA106480C000F5DB507DFD0C7400400515D81FC1F8E32B10BE02F06B9F3FFDE947908002F41F50FF07E44FD0FDFDDE2F91C2F9DB23F0BCFD020850BF05E4DFFFFE00F5B76F020DED573043FAEFC72F80FF0B4000BE04C8A77082FC22A45040F9E7570040FBF713D04000160050C0040E884F81FEDF9F0081FB12D02FFF0211980FBFFD1768F080F8E313803EFEEA6B2002FEF9B3BFBFF10B7820C001EA0FE00001F5D37FC1FA04A0EFFEFC3CE4EF7E02D9234080FC198070C100E417504203057860BF07F68360FFFFF82380C1FD1C90B0BF051328203F02F9F70F8107EC435080F9FDAB2F40FEF21B20C10302F02F4104F27BDFC0010258F0BFFB01986F7E02E7AFEF7F0418C4CF3FF4EE0F10C007CED36F4209FE5B10C0FB02F47FBEFEF867E07F00F9BB1F41F40540407D01ED6F0F41FEE48B8F01FFE43BC0820CEFA38040FB01AC2F40020F54E080F412D4CFBF0118A44F0305EE67A03F010B04A03F0819B80F8004F09350C1FC10544FBF00FA7F800107FC9BCFC00724F82FC10A2F74C0BFFB0AE05F8103C9D78F3F0601F02F7DFEF66F107F00E85F70FFFB1678CFC1FAFC338F7FF701E86FC1FFEA27C081FAE81FD03FFCEF87C07E080BE4AF81FDEE63208002F41BF00201F83BC0FFFA0BE4CFBE0416E8BF4004109C408008F5A72F7E00DE835FC0FEDE93B03CFFC7E7AF81FFF03B500101EC5FA03F01E0F33F3E0600B44FBF00F01F200001211C40FC010D8C200005F7DB9F7F09FBA3FFC002070C7080F80388AFBFFEF297C0BE0AF92F507EFBE3F79FC0F7071C5040FE00E0DF02FA1FB85FC0FE0F504001FE15846F8003036830C0F7DE6FB0FEF3FFAB4F40060FDC7F010DE80B7081F70D741080F813CCAF7FFEFFCF3F81FCEFEF2FBFFE07E0DF7CFE017C30C1F9DF673FFE02F11F900004203C7000F9E04B40C205F107B0BE0225EC9F41F8F9D77FBFFDFE13D0FEFEF5CFDF4000F433C0FEFDE3CB9FC2FBED4740FFF608E80F01FF022C8040FAEF13A07D02F4DFBF800331548FFEF9FD03903F0312B46FBFFE10C0EF010224F0BF7EF90834D0FFF620705000F4F55F10420021DC6F41000D2C5080FFF11BB07E021CA0CF42FE2FF08F8012FEB3807E07CE571080FFEF9F5F3EFEE30B80C00512CC7F41FAF96BE000FBFB13B07EFF0BA0DF8202E00B9041FDED4F30C001F2CB60BE01EC0B20800102E07FC2FE1710D0BF0B231C903F03FF1FE03F0533F0DFBFF807EC2F000917445040001A58EF0001FFF36F40FFE627B0BC030E3C907EF5F2AF4FFF08F2D72F40020E683F7DFD049C5F80FC13F42F7C0006949080013118207DF6FCE36FC0FD0D348FC002E4B7F040F9076490BFF7E56FB0BE0903B04FC1FEF623208408F06F303FFC128C4F7E01F967603E14F447908009D7E70F830BF1F30F7F07F9B31F42FDF57FF04004E3D3BF81FB1916EC44B52B4BA993C5FBE705800B2C070B09CF36E0908FB89F71E2F7AD944898B305452F9EFEA0237F6BACBE4022FC80E5DE2D66D398814A7C835419435744");
-        byte[] msg = Hex.decode("225D5CE2CEAC61930A07503FB59F7C2F936A3E075481DA3CA299A80F8C5DF9223A073E7B90E02EBF98CA2227EBA38C1AB2568209E46DBA961869C6F83983B17DCD49");
-        byte[] sm = Hex.decode("553082050F6A8BD54C40E296C67011C2765199D81304F22C3CE306AE9C05235FC8EE08D851E2397C2956795D02F1AF944E54E2D270ED89FD6F89FE9BEC269B5CBE7F0184B244F89455876E181F39E1221F962B34FAE1A8F6E4EEFBE882DCD5BE4941EAC3ADAE263EBFE5AA9E0BCE6D2AEF44A12710B4301F07545EE129974B27536C2DD6C38FE9D5F7B725DA66C8B7A92F439C6721AD2CFC389176BC6A39FF44483C06B9D1A554164F5FCEAB5BF317983E8525ABF7EF291DE0FA281791680F61012A06742836CC6E0CAA87BDEC0C7D451801979D3A993068DE6F601DA81615FA56CB0A4B315CA2CE06F21925D088DE4DF0CAAC3DD33E51FDBE8C0DD187BAA82216009DD2819D524BCFF70012A734FE9D936A413AED15B3D652258CF6C3B0FD5BF4F64B7EC9BDBC9251C01A34AFBA707B35BA4EB6C392DFD6F93EEA454DCD2BDFDD689B5A980C21EEF124843F5571E31A69AE097A25B3CB1F04ABE269E78E098B658201B8BECC456147F759B14C3A3CBC00D1D7E3344F05E433C1C82DBD4001C6AFA8D948E92D1686C64F57436F6DD9ADFA82F35CB3814DDAD7AD0912B62B6B412A40D70F49E05B305BB5DD319C5C89775F27D3DD1884B5936A54738F816AC81FE328AB344FB39CDA6EC0B64D47556733815D2D83818D88D65094933E6E9A9FEB7E433D3EFE0B4C98D7ACCB9EA851AC72134283116513EDE0802BF959ECD5A8AFA9407D8EBBBD8ECB48B24CD034C3FA763E83782D6B0B2115929F94645F9378411251692C8858BE725B9D8AB880054956E71A75D5DD3A88D1476C36064A3C62EFD861C4A25943037ABFCA2DA9363F34371E868ED76D48E068C3A078757FD8E8588B356530F098ED81E3C21652E1071A6DE3F8610B94BEF7EFFA5C5A955EA3987B0EEFD16E6996BBDB563BFAE6C769AAF95E4CC4E42634A0CA641297642C92A57AA8A704E35CD8A759366E1DFD2006C173D31BC853A1089284F043E1648DB047E944EE26AED13A07DFE7C6893998A43CDBF9520A9E077DE27B36F03FFF08FCF4C4F6F4076C54EE6CD7F93C7C1297A4917B3E430981B312C2E1ACD923A419175C735F80F3957418BBACFF5F09425F047564E2F9FE4A9445FDEA63657FD25D2177A9C5A2BA11E54B3D0C06B2323735622AA23A69C14B4AA78BC4310481067E68C0225CBAC0C40A21E8676B5D2E82DE06BB5BC95A3A4269747E9BD3895C0C29A4B25BF85AE0C46959B6AC8BCEBF6CC3039FA0AD7E4698CDCB73C44A10A3469520C5967C2B491777711DFF1F311FF16639A5C35F10FF7DE14EAB5A786E7CCF152BC45600CB3565BF69477EE7BFB508F3259C75EC87B7F57561ECBBC8178497F6B77D5C8D6C2D3DCADBD7B2C21B59333FAAED20C5152E35886B1C672F6AC4BE7FB0DC6D9F4478428141FD35E30691677A1E6B866CB91BB9962D4BD5462275D5932820FD104BF0E1D4E08F0CC2B7B1FF36D2A694B848F1B84475FB647A828CEE4DB1566CA35A908562DCD45AFD107453E4FD18B571B2F64B077DB9AE3BFA15F0C10BAEC410805EDBD34B6638138651F24F91F9D82D8E7CC07F1F5AD93B1017A375A4A9EACC85A96403442BF1D5D9BF18204011E666569B6F280B268BBF339B607D7C33BB59EC7E12E7F20B14BE770547F797E990E70A8F4F1B738B4EFC7CE124B919537CCDEB5F0CA8DC2EA9649640EEEED11489C0A1C110DEB64BDA50C756A3EAF34C4E1059560D3788FDACBC2E352F2B93BAE31DE89C0D19277BB8AAAF0A419C2C4C1B265BBA1B41A9719AE53A7DB5F051E3B08836E5D3087A94315C8B83E21B1217E1276FB90252F56E72CF9C406A9539F782B4BCF95CE4E55340A00899CEDA7A0C020AFBBA1DEF8C394CE2354E86B7752DC57D1CC94ED32A27F12A06AF8C72AD2BB0A4D619C7F1569EA066B8231BA0CDFFA761F54F1A574C99E1B24C53F33F3D1F34225D5CE2CEAC61930A07503FB59F7C2F936A3E075481DA3CA299A80F8C5DF9223A073E7B90E02EBF98CA2227EBA38C1AB2568209E46DBA961869C6F83983B17DCD49");
-
-        doTestKAT(PQCObjectIdentifiers.qTESLA_I, publicKey, secretKey, seed, msg, sm);
-    }
-
-    /**
-     * # qTesla-III-size
-     */
-    public void testCatIIISizeVector0()
-        throws Exception
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("2773A7AD9D114C05E70C654102216989125420019CD31541E8A191B142C2732FB3CEC5C9C070A830C50A960E4B07C8108AAD0C044AEA0CCCA544422AD17966A52704BEFF1F2BC92423D79C128B891392824FD4EACDBD5DEF0CC2733BDF9D0D0835CF08640BB61DF4006340013144DC913AA00D411FBBEC59D5A3D20E1A3656319718F82F683978FE820C2AE48349FF42B63F44E71DA1678C2ADDD8447E9B30AA7D083A9FAB6CDE4B44AA9668F1CC4F58432D55340041792C06F3ACE7515DC1BDAE4DAE329B49FB3C4F086A0CAA846AF3C52721C1DDBD2A88093563A6F9FA11695CAC38C838024008B10AE0C76DE6272E0198209E133FAE50410192782130B838E56EC611E707140595C9B9620FB0E547C10A757EDE0FA29606028E8200740307E9ED8870D9199E0976E30F7C4233646209684B2AAC28E4B343C28008B4A4FFB8E02AF2D01E61E20F9C5E02886DA498AAFAB60A702AA4C2E050A4691FA6DF8ECE316AB75A61B55E5381606877488C630D33CE27382BC5EE368C5579F2620673E79789B1C79C405B97A426E09E49090BCEA3FE40E01513EEDD9081129C0A0A3B5884FCE5CB3FE7E6B37BD08FF1324ED390E507CCB3271150B1D02F9F15ADC205ED0068E141D50DC078D37E4E7E283510115811AE1205CDD1A109BB114771C0D4FA9E8129556F1F505CDE6E0507B73BB32B0AB3EB84AD99586E3E3326A0B1BAD6C7A8E0053D18368AA8BAAD7D702618EAF33A3B5F17E5EA410803E552E4262256682CD27005C054103B41EC37EEE354CAF7AE0F19A75EA470616F94174C72F95ABB4B9523CC4F291FC787A990E8EF43D2B8C1A76AAEC10C095F1BB0111E1A4046F6D131E307B960523D1884ACF914CBE40FD3130272510A13133123AB70356430620A3EBB2728172BC56C0F55064C7031D6A461693C80495930C25A04F61E223DFECF29526CF8BF261FCD8AF44CD13AF9E8AB227A579F35B0955AD56EAF7C012265B0A839D951B93C0E6A2108942B82C2658FCF12E165ECB57B74DABC2D30CA246F9B4FBADF654C011D00FA144170C9E9EBE1A935D620C43EBDDD8D1E60C29EFDA04CC17407514887F8198CCF58092CC75C241CB34AC2F9477DC0675E8CDB2D6934EC7B3EC25EBB370CDFB79072300575597754E5609BBB94788FFC5E12E713222BA5D6A22BED445E13E170AAD9ECA0D26E22CD0B808E1090EF44D9BE210E426765B9CB2D75954316160A44C3D936EEB21DAAB7AB1E60A4F651F3F5003061D80E0E17C0530D4F15474C2B861B72B3FBA058CCBD14568236D40E6B3C359FAE089388639703057DD09806C9108EE6843F02F03492C6B70A421B01DB32C10DF88AEBAA3CB45D4CC0604479AC1CCF0C9CD76250AC3345BB60BA081462B1667A1A5C772B505B5D705223E8AC30FF1C423783E1977E62277D196517636CDA83A80A87AA54087CF099B1494DB8F63C166E183CD3E8528557038D9D826DE01963B471E3337093139CEC888E6C5B1249826D966355BDE17CF989254C8C35036714852B24D89BB666EA8AB5AA68506125ACD8A51848478A2C14C94F189578C0ADC8D04AC0E896F6357DF3AC17172B06B5B25BC4A41CE3AFB266E17F7990327CC174001F71ADAF8F5EC08404A3A74B80E4974D20B631C11477BCCC1164AB8F49F7D6F57AA4A77F724435C218108DDBA508285D3E412A8778035FED2E516DA7BF990AEA03FD7E91F05537FB5A962A9259A0750DEC465687304925CFE5BD8F044A7A1EE638D2C448235D2BC2C9958C21DBE152B8D9865E336B57911AF86F404723CC995AE595F888FB675C47C2BC311F60BB3E47B4125CCCE51AD3A231A9792DB6788A54FC2935E22313A744065387AE450F0168BC00F60BECE76FA41E5383B0B45F950B27D327379865A73C6239B481FFB4AE78757CF51F5ED4CE20A4C31E89E19FDA2959158578439EC56CF6D29B0B2F8C307D63A13E8073E5AA7CD2C0FBE55EC4348AB807E773D726754F72EDD526B788ED61D031B3C2F64803AD34CDDDD8DB86C0078AD3CDC728B0399401A0C28E3D322FBD576FD97CCA4EAED9146CB3F83D0139024C14E249ED6996FC6182E739C72B177149EA08FC991CAA73DC8D6729649F395658B33D49B21D221D65E0BC90A0AD85E0F714D06E2CFEBE84F163843B7854F9E83A0409C07CBD22646C1A841FAA6FDA61FECC495CB2C881CAEF7EF4D8CF54B52241DB36B68093D40880822E808D40C2DE6D0488F476FC79064A0C8E03A05C02C6DB20F3EBA8E0067C154F2CBC7C4849CF87F7F1C92B4EA8BA70177FD1A31F691168317A3D4CCF9B9EDE02DB044645622313807E49BEE1D481136EB26803CFA5756E177EDBE1AAE6B0C8541B4FC48CF01014E54B2C789241DE9C54ECEC921AE7AF8A427213689A4A2CAB6376719C25A896B45CC414CF762B91A9840210095A46F3281A6DA086B254C916663B7941E4CE5281E33D8E3712226B201DAEFF4ACAB7C504C727F16D2F11C14885A2ED080638B153A40B3B12457A6A3AEF950BEFF282A079185C64FB1CACF19B8182900F2C4EBE28B7E370A3235D45C6F484FE3F75449E499F07F2F0A4FB1AD6508ED8CEDA34EC98A4957EAEBF25FB8F43D16F6DF3E32705A82979E221CC11E63B0C883D1EDABD4CD5A0855812477BCC726D68F0B211420C068D60BEB2D2CE9DA32EA68657432CB2014904BE4248FD08DA1F9C5E455B32A1A1C9AC31C7E7A1ABD8BD363A38AD78734D1EBED954871C274A5D54C1B492F01149BC90848E04D026118E9DDD0B84C1FF22473C08D267F1581D566C5F62EAD72C79D75F565BEC6C3275ABE3638D2D003CBA666B5AE85AA6E33D807F72AC157F64390320F3D4203D0FE2B70EECA8BC174D98AE7461444CB33688FA8BB82CB81F84DE6085BA9B63A9098A07E78DBFE5A20161366ED941116C790C67D7BE28680D3372C19E7BB17433903ED4688D63D4CE54DBC0BABBEA4916FA49AC71377A7E2812BE6512D504C04F51A6B01E625E806DECB5D3F6E19C726EDE8428DCE04677B2D8A7083A8C5A9304B06302F01973A169CD4A0D04473F769BA8B08E069F96EB07AFA0A5A061723B5C537BB6B41AAEB03F736F1B00210CDB816109D148692D49854DDA515DF53E9F737E594EFAA8033D576401CAF82ACD6CC7E8E913BCFF0CECF25D6C8BE37CA3B0D05EFB405EC144E4BA8B43814814B3488C6C7844924F77D06ED8E9D4090F758C13226577265B20898D40EF82D8410AC9C807D6BD0DB7124D4F23468736555B584BAD6864130027C825F8F15AD4A6550A5BD9AA04C69D38D44BDE53B5DF9EB202347876643C250278D786F8D8C4A8C9EC33FA57D8803909FB0991D370AD2FE3B6BE7E0798D4282C05B27410DA4B1718F05C82269C3717392580BF0391F20BA083057D3AA392E29D5C81CADA50069F812E6B8EFF77C59CEF51D27098AAA219BC59D4E549767435766344B754AA70FD43877CCBBDCAE4D0D6FE8F30C94B87C82ADC3BF62DFC6683D910B13D4ABC4B6B68C47AE51317F1F51B16918E7D9E851C653330B01838ED10601494C8BD86328405C18FD20A62F384129DC5AC3EFC58A915A182369F1A1F088608F64B314FE1BA543325B72986612765D3A474CC9A6CE04E1ACFA49BBEF23DD0B0E4E40C320578C985E8C29A653AE7046016E067887E85C9C3CDCC456CF811A256FDD8F1E9E8660466372EB85BE369D3B496991A7D99198794DDCA7B64B4BA91BFD7A3C258B170213639302B4EB45A8C9CE7C2A79FE3E3FCB0C23B057025CC207A6D02FABA9E299FD95525B4DA20250A1B54FA196210681961A04B9F6862C5F87586BB90027C06E9B769D15CB06A7867F7329731AD18DB714980DB021C8A93D32A20858AB61A741FE8B6CEB89E6DCA0960B06286AE297A5FF8B99A42660B3B802C330B91F686E6F314B62B2C83E8512D93C5E29D689AA1AB43401D0A866586BC937F66A3D0711C20C9AC36426AE20041358AE8C3E1E6AF31E4DF0157EE582210B6C32F346481B7D888D34DA15238C62E1B43FA4D5C1C67F6EC4F25A62B810418D3512B81CB107D9E88711E64C4F224894CF80D09997744FD7AE52446A7DA4E10051D597833AE1B36C86A0CB69B51C27FEE1B0A126FD88AE54BC04291183C0919C4912DD5D4E9EEC9C40B65D58DC33CB0289B0FB60D11B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD");
-        byte[] sk = Hex.decode("F60AF7FEF8F7FE08FBF50300F8030CFEFFF9F5F60103FF0FF601FE00FBFA0E0003FC030007FE0201F401FE0404FFFEF6F9FC0209F705F7090E0002FDF1FA02FB08FA010602FA00020B06F804FDFFFD0601F60400070100FD02FBFE000B02090308F9FF080005FC08060103030801F90B03FA060704F9FA0FFB050CFAF412FB0114EC070BF4FBFC03F204F606FD0400FBF400020806FBFC0CFFEF010CFFF9FEFCFC01120909030CFDF303FE0200FC0600FCF4FE0504130D010CFC0208FFFC00090A01FDFBF111050911FCFE010BEE0302FCFE07050A04F616FDF8040D03F7FB03FD080503FAF90003F70A00FD0AFBFBFDFA040EFCFAFDFFF100F507080109FFF7FFFD01FF0506FE04060B01F7F9FCFBF305FF08060604FC0DFA01FCEDF5FA020707EF06F8F00D0800080EF0F6FA04F8FC01FE0102FCFCFEFE000306FD030601FF0308040104000302FFF5FBF60AF7F0010B00FF0009FE090002F402FD02040F0100FA0606030205F71200FBF70909FD07FFFBF107F703FFF5F70FFB0D01F90301FEF30802FAFDFE010608F80BF603F8FB0E04F905FF0CF9F40EFB0B04FF06F7FD08FF04020103FB03060208FEFA0CFC0A09FC070BF5FBF903F3FEFF0312FE09F9FC040A0EF5FF03FFFBF8FD010E03F708FC08FDFCFB0C0AF805F90EFE07F5FDFCFEFE0502010801010601EEFDF307FBFAFDF903060B0C07F800FDFFFF04FBFBFD06FEF8090A050A05FE0A0602F102FC03FDF711F70401FDFB00F4030DFF0808FC070405FC05FA0AFF0EF200F6FCFFF8F20E0AFFF8F7FCFF06F708090FFAF6FB04010207F30402FF05F8F2F0F8FFFF060206FAFD040107FC0AF305110E0502060B11FA0500FC0BFCF7FCFF09FFF510F802FD00F4F807F5F8FC0EF9FF0AFBFFFEF6F902010BFCFCFC04EBF30703F7F30204FFF10A0BF80D01F7FC0B0701FDFAF300F60204F60EF41103FAFA06F7F7F80304FEEDF205F9F9FF08EF05F90DF6FF0307FDF206FD07FEF7010307FF0600FAF7140300FDF70410F601F80C01FB06FF0BFAFB0209FC07F506FD040A040C0DFAFCFF08F5FDFAFF03000503FA07FF0601F8F30A020201F5F8F9F4F704F60203FE0505FC0A0C02EF020204F7F5F60405F7FDF702050202FA0B06FEFD0FF7FFF201FE08F806FB020100F904F302F503FFFB00FEFBF6FB08F9FCFE0407F60609FE01FE020105FEFFFBFAFA07EEFE0E02FA01FAFFF7FDFF0403000207FEFBF804020008FAEFF6FC0E0C0604FE04060DFE0D13F5EB06FE000A05FF0E02F7EFF700050107F5FF03FE01F209FD0301FCF00204040203060208070AFCFC00F2F40BF6FAFFFF02F90701FD030405F50606070600FCF6F609F5F504FD0D0A08FBFD0207F3F601F503FEF404F7090403F90101100E0000F303050104F4F20C04FC01FAFB01FF08FA02F304F3040A0BFE0CFB08F80B09FEFC0105FC0B03FCF1FB0904FAF30204FFF2F9FD0B0201FDF602EAF1F9F708010000FA06090301FEFCFBFEF4FCF9FFF8FCFB03020A06FB00F5060704FF08FEFDFBFE11FEFB010202FCF300F90C0105FC0604090402F8060A0A03FA07FF0C0F0C04FDFBF7F7FF0402F8FD050B02F801FBF9090C02FBFBF1F30B0901FA03FB070300FA010403FD01F303FDFF0F0209F7F506FFFD01EBFE06FD100F000EFDFDFC03FBFD03F8090104060A0900F508FD090D0105E9050305FBF500F707F70503F90706F10801FEF805030B02FBF701F60007FF03FD03FEFD000808FEF4FE04FF0CFAF500FB13F600F5050BF50CFF01ED09F6030305071B070C040C0206090205F103020508F207FB09070A00FB110502FF06000811FFFD0B0D060401FFFDF702FEFA001209FE09FFFE00FE0BFA01040308F6FAFFF6070309F5F408FFFC02FCFEF7F80B03FEF8FFFE01FB04030304FDFF02F90AFC0A070407FD09FEF5F9F3040002F2FE0D03EDFF0305FE010B08FFFA00FAF7020106010D0A061913FD00FA05FBF808F404F9FDFDF0FD0403FA020200FEFEFB0004EF03FD0B0301FF010A02F0FEFF06F6F400FEFFFC06050603021005FD0603FCFC050A04F9F804F40E060100FCF3F3030BFFFFFF04FA0705FC0E04F401F9FFECF9050C07FAFDF7040EFE04F9F6F900FD18F7F5FEF6F4040307FE02060302ECED02FFF902FFFB06FAFE0BF90100020BF8060008FFFB01F5F3FD0501F60D0B04090204FB0508000CFA02000AFC06F8060200020606FB03F80A0D0800F501FF01F90DFD0203F4FCFDF8FBFAF0050107FD0101FD07FBFE00FC080807F600FD0801F20200000CFEF5100802010206FEF006F9F8070B0712F6FF0600070309E8FCF7FC0BF8FC00FD030F0307010807FD04FCFA08F8F6FE06F8F6FCFCFF06FD09F5FCFFFC04010F01FDFE050E0D06FD0AFFFDFC03FD08FB00F7FFFD03F607F80707FBF8F90108FBE603040406F0F303FEFA01FEFEF303FFF202050203FB0407021104F5F7FEFB07FEFC070B05FBF9FE0CFEFB00010CF80C00FBFCF609FF06FFFCF9FC000201FF04FC03FB040004F8FB0B0301001001090003FD09F9070002FA06000D0D100508F2FCF4F10B090501F90509030104EFF2FAFDF903FEFCFE0906FD00070802FA0304050C050A05F90AF7FFF9F704FFEF100005070113050CFBFCF8F30212F8FDFE030505040C09020201F70BF3FFFF01020FF7FE0005F8FBFA0B04010801FD02FDF601F9FF09FD04FD02FBF7FCFAFDEF04F90801F4FE0FF7FD08FDFCFE05010306020EF70201FFFE02FE040705FB0905FFFB04F100FA0A13FBFBF60AF8050803FC07030AF9FE0D01F7090E0009F8F4FD020401FC060607FE0206FFFCF8FEFD0004FFFE0704FDF5FB00010403FA02FF0005FA02FE000AF207FA05FD05020504000BF70FFCF8FDFBFB00FFF5FAFD0309B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB1");
-        int smlen = 2753;
-        byte[] sm = Hex.decode("FB07A3E1D8B107FCA922CAC88C668B69F9BD54330712E3788663FFB44310B4D5057F5F479039A87C3B0564A2474D1A97B944E852300044C68751C09A7A5B8394D4D388456DE8535EE57238F2364DAAB11BE3983AE492D5AD2C9508CFA2A138F4666871BFBD394BBFD69E577E812A13D973E20D8B3E63976021D946B0B3DD53D343C344306B85942927A75EF08D7DA4BC31DA2B0F727B997BA243BC1294B30CF5399F58D9A1EBF5F2D4E5D3D3FBC580FCF2F6EC2457EA4C713B5B0CC6FC565331E9DC92CA419CC73A4D618ECF6A5B2EE146B28A87458D332768D93C3E01A43DF628E1EA0B539040B6D5566189FE8260A22F701EF93907B061275637359DB9BB6361A680DF9FF38B4FBFB2BB31CE0D1891CBE07C6C2F8E0832038239E92FD4F70F5BCC28BCCA1E9F800266EDD0CE007FE86052F1506DC45A8A9405F13EE63C963019776D11311AD44A01E5B27CA4C51E051BEDFE0DBB5A3A7ABEE5B245689AAE0470D5803D1308D78D8A15167F31D87A89E186CA8B5F6566CFC7148209485AEF22B1735FD487138A261D35BDDA857BC0A966A8D407463D1BE9EEF2387F2F93B1A835AAB97A17F496AD92654F5F70DE317120365DFF755077168DD6C3E2320BC025E361A6EB98873DE8F9228A223BAE4138EE114B926C48494CBE6065C08E759109BD18C41FC740057ECA699369737E78C15FBECCD29D1C6406819511B4D115C238469E7D0722D0F17756F2F9AFDAC4343500AEE4C24D39F7C932270796E110FDC2739DE152A42B494C69E38A2909CDF0FEB6E4DC8EC028562EDA3109ADD188CE142B8189565802D648067BDF6497E61758F9C893100C2FCD678DD5CB17699ABBD670EFFDAEE6BDBC510727A48ACF0E4FAAE0E7D9A04A27BB375671048CCB3CB2C69AB232A9C31F617B7608ED88EE44696A2F75F8F35D1938430D482C67861E7E63FB71BC6925F66D793C9BC30D0F11DEB80BC23E1AC43E5D2F91F4DD216AC0FC0379A8F50F290789E72D322E9F75433857D99E5B25DA53F181B52C61E1ED59B99D7D86333C69D8E7989F37FF92D3B51125ACA0C477765D2710DCB52804C620E39E85EF9F01F9C49E5C123B898952CCFDBBCE985CE9D3E3D9D7A3F254FD10614899B694AD59520CDA87DAD998A7F0F4118E14EF50470F3B970C89702849EBD5FFE168E387AE970318CB5642D46D8582013779EA03C956B893BD2374DDB002A14BB0FC8E2098E1612E47703EFE0A75F35B751EBA9539E02915AF108821C3C908315F1C34F578D077A78F91EB6DA12CB4CBAE937CB7246A0C56A9F997D58A180653FE8B5DEFCEF1BC6BA41DEEDEBA6F3D6B695A91C270AAC086D9CEBE4D04D6D939EE0A0C1201F10C39AC3708264DDBD4B1A1B7D245304F8A6AA4229A0F7599837D9ED334246B719C102239C7BB93C0E9EE6EC9DDD57C59D7B88E43E1C19206F356BDC92873F8D7A687FEA9408507B32C2F4103936FDA2A694F486E05129D69F36563ACACDE2D60F9E114502751C2C5A5EB27D8DA8F497260A7BA003A858802F26A630B69A554CE006D78223D400E7E757AD8760D1EE7C8A6AC629E28C5CC0BA8242FE96203D970637578BBEE77466A6ADB19BBEE0B3C894EFC692D200106967F53AD26BE7ACFEF2D3ECE609B89F6A940FCB948D458E89798E5DBB2D5EA7C75957FD9F53F58050CDACA4CF5613C4460C4E3638663D4E2A4975B8271BFD26C48207ECAB05712E1651C352B0621C3376BD713CF3A81DB0D4AB2C3BDB1EF54775BC980BB04E3872C9119890037AD8BA2A984F7F090D8621D442383A2EBB4667F885772163978B346ABECA297D983919D80745B061154543E1C36271D9F5E82D929D2AA007025E8718324E260E992528D0213FD5A5659398FCBDA4080B34D6AE7C7C7DB595BC0A7E0887EA5FC7FCC76668214DB2BA457C2FECC69A37D39C7C2009BC52F7F4304C26A260994395BCADE9072351DAF81D484876ABCDD876FA87CE76E5F024CC8D098351B777D9B908FA5D903EDA4BD732173E974DD36BA039FC30BA6F5EC7C60C507E1EC4A7100CC3042EC30FEA3905998483CDB77D18118C547805C41A26DD0007F73BE9447590DC544EBF8D6880E7E61104DDCEEF39866DB0868CF9C8C0EA3BEC50B2E608540C3BA1AC7D4C9C945EC830A002C15AF68BD349E5E2704F6C244EBC927051B1F04072411AA7299FE88A38D1963FE778F5D86C34DAE4983BC2B6152E84A65E820AACE1FD7BDB5E22606AEB8590EFE2A7A40D4159EAE17D5F4239BA3D2ECC659DCDD7406D76493D4092F0D4FFFBE97553A26D94B4DB223A8B62A135AAA6C692BA1DAE42929B9D4988BB0D7B06B14A60607115AAB791A3AE54D5FC0437954B9917EF638F0EAFBDA8C4AD68C997E732C071A49E6AB0FC66238629C6E027C1EBAE1FEF45874CB28C12298734F0E0BBF838BE199BEC0CE52D206EBA2C4AC6230762B476A36042ACEDC9DECA3D36C92492D998230155D08522D8E11952A3BB06128D11EF142A15376EC172B66294A7006647DD1831ABC0034F0E0FEF507D988F0E60625AA76A535C2388F8E7F5DE8F59997883280BE1BA98B7CE54C797582933B688CD48460ADB2602A82912768A34F9E911F31BFD39094E7D8918F6DA0955F03CF44C0D4B37FB331727F1C36CD130A9CD0CFB88B1F274EB3D58E82E0852DD6C80E0F368C43BA2BEE67139ACA77ADD2633788AB994A29A565380118FE843A74178FDAD308BA5F6A570FA337A6B1E9BD5B6018AAD8BC8C993FFBFA7015D7337477E28EC09D6D9AB9AFA9CBB96DC46F9F9304B0AD829D54812B8FCBEDE58CECEB89E7A992AC0400E7BF0AE2F90768D6EE308C78DBC7F8D0BABB8AEE8BBAD5DF00B04C4C7D70F8B3F0B6A4BEAFAE07295C27EB2FDA2E15C1D0BB5B4318944117FEF36A0A94BEEE967D56A679116151FA916727B66D174824D13DC80024EF0FFDE2C4C24781F6D24ED091F4CFE61B2151E6BC6F5DC08AFB5DEE5E53C9897805D76363DCC57B42472C57C8DD5AD2D028AC0AC9640C14BFB18E8D6D4E6CC505F68AD65C49693C42043595098DA51B7AFFF60D7B26EEF522B7C5D1281B5962E3DE06EE6A09253897379DCE3285DA42DF236431EFD2E60E255DB71F380FC58DA34CA88E86A243B20486FE52B6774CC17C58BF1A2E3978A61FB046EB18014F9485752A9BBA5D9A434F51D0C0A2AAEFF67B801A996E1189BF68A0D3390B94595018FF6F925ABC9F4E12C90B8DEC1BC7D2720F658826CC53082397E19806DED90481E73F99578AD4B1D21FADF9E9D808BA8ED362B3F86E6EF491B13F48F064C22D278016DB5DDB264A4A0A18382913723E8E3FAA13057872D75C158A056176DBEBA30D7C62984E046E2188DFC3AF9C56149187BBA63CBD1D52798B4FC90B95081F5152E4686D3B9293F379F2ED37F365FDEA9E15024FB028C4C1F76A49EAB005FC92692A09BE757CEC084F4856D28A13C36637688FF369695A1FBE9991CF2CA3A10C6646B36B0ACC0CB00BE7BDE17682842690CA18C17E690D937E55098C82FEB02907B83BE2069B336425277E3BAB065614A44FCFA606CE390D504E72A8CB79745C82AE0A259713243BBE863ADAC3496E9254F2BA2AF5007EEA92413327FAC766600619DFBF7C82D4A8B53C50EFEDBC76A199A8CA003D5BBF56BAFC0DBCAA0BF43E4EE53A0DCBC4C10945CFFF2398377EB3DBA89710C0AC3C38E484645A629833B204E68E03D9C02322313E70F20B2E0655146FC8E4D94464DDAB904534FF4A53FB8D5FF9C5B6787AAF969275E817D66525C07ED7966FF96E906FF6F9A3F0B61788389C1F779DC4E6457DB0C2E1056F28F589021211450772BF97643DC8351809DED81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-
-        doTestKAT(PQCObjectIdentifiers.qTESLA_III_size, pk, sk, seed, msg, sm);
-    }
-
-    /*
-    # qTesla-III-speed
-    */
-    public void testCatIIISpeedVector0()
-        throws Exception
-    {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
-        byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("624563CB6E4499327172AF7F15FE1893614871835EA6F3330DD94337204F88D911FFB91B06CF2F07240203F75353906FB7EA7BCD0A4362546D94E60D32833623CD73B1D055DD1C2533CE5B81DE05C02966275D289D25134EF8577C7A489B383D3FA769DD3C1C033325034B731F7F678E5E58426F372F0F24DCEE455A8C6B0E6A68796E7B8D36317B23641E7D4EDDFA1E4F4A5012F50E70222E36527A14A559F4132F025F2C20BD3C2C023AE2E249E94D23460C80B94006CB1E38769A510911672D2E48AA5E5DD54F38746C195CA954D6A909CE33759A222399F209FBCF26641029D08D4C0C3D51D2FD7DAB2A6A3779365B06720B686F052A1FC5A54C5B4A26962F224C194096FA7048372F037D5E9772315B956881615C72EB57A0B661E9851F5BB61A17755BBFB571CDD90FFE562C4F147846407266A43AD9D063CD7710256D69FF6F07164E7A41D13E66C60614EB7F69B621F83237633E36E7F16605DC0A89A774E425323BCA551F211D4AC6110C314F39B873E48062C00255EE6E7E5BCF75A94F6DAC0E182C44532468658A2A6A9A9B72BAD7715E0765A10B520CED236A4D3343FE2A347D726D6107046877FE583F09FC50F4804CAD603810A15C7BB655400533721E4D38E020EFA64E3DA8277DAA437465033C486FCAFB5235B44C76AB27ED5E5D7BFA6CB2EF6EC7AE008AB5320CF861DB605321406ECDB56837572048175831343A1A192E7844225B6A72E66B42C98C7A3C513D2B5A71BCCC329BCF20246C241C4B5EBD5F37ED144C1CFD173F6330F78D40C9A952192201126F79EB1E444BE27E3FF500FDFA0BC27706FF2A4381454634540D3BF44CF418613C087776C940F5EC1928E1190F710909EB43EA715FBAFA3E3C140396EB516C9664A87277AAFC139D343D512C65776056A1C31104645033E03EBEC24D99837D1A427CFB7B72DDF86054744ABBD62B57A8355C8D3994F84B2A4B2CC1B731529E495F811A73414C03FC1B66BB0C58105283F42AF9B2521A5B717D8A3E8DF411A85C68F706006E8B7D1114769E2103FB2B7601DB1BC28B0355D909AAAB3702904F96BA6FCA3152C4F042596A7854B13827222DE0925A47CE40F2AF3FAE9F19395D70DBAD28DA8E7AC34F5BD36E174CA352807F2C49645B05015B389B3666A679AF7A033C8C0659600F6761263ABB6B4BC2360F29267AC82D80905502B7557A295406D605D75828565046DAAE09B12080EEAE67179D53B05A54444B4CC77C1E2DAB2D298D785B841047B5665D3C36E3181CD7AB2DD49F78633661949F585E0A18530D7940046E140D684C033915EE5DBC3A07C34A4178516F43964F0E2E04647324D26107980F68FF990C7B8C0C47F12FC1AC384520485EF061DF373B78A12D80810834AE36E5BA37397D35AA5C4625B708C51066F5C54CB1C52E0A9D3CBB772819D44A875B78AC4A65D3EA7621BE271914770B8D363CA911B8874CA1C16FDE314725B409391933DBD96B5BB013C2723E307D7D9C1C41654049204837D4074FA12B46E25D39E1EB3F74194C08CA47453E2092AF3142D06950FE14087E1A80970ADE4E7C6F6D70294F3710743854AF2D0A1776DDE166895950DDF06B2ACD11E5A248D8CE52EFF276AF9414B0DE264B315D61413EB731154CEC10F0FC230801229311566E781A57F65EBDCA4EB0CA71C62F281AD139D105108FFD5345016734F37C039377F71B36B649661F425A2CF3132CD40FF6DD5668C25808441EB86415136A55F57A1DC5991463A00BB3C67CED334979BE64A192471EC95F9F591014D607BD23585E5F0E03B33AA6E11E70F3314E7305336F5BF5E25E25D33C985B2D86BA0C41980199C506CE420F12523269D62949904174B86B383E41255F64940029094C0E9BAF76636E6E37B6711DA70ED1510E6BE35DE7C34FF8613BF3AD2358161E0E7F58696A669FD50ABEA278872C58BC3B1387F93DA26245E1DF1A35D32BB51F372F1133975E5DA27B5E112B43508D5588EE21D7A2704A9F7B1E7C62454D6D4321171C3647841D4A7BBB3B25DF019F856B7DE1652F26132C55049BF85AAEF21FDDFA03BB873761A72B389B3217C238B1EF315D4778EF4643431E1327350F240040CCF90C324370843554E9C23F6075089FB94677E85377B74FDD6A174F3B628B3F30778A629F6A5D9D8274943110095C3B763E4BC9862DD3F1441DE30256F20FE5DF629DBA55AF182C373D03D1E707253360E69455388554FC8140E97377B38024DB1C1CFAD014949552BDE132F8BC1D066166B48566C4A672B9F07A3C32399CF351AC073665FE4D52D11077CC6E823516920843FDF62FAD5D7D081B17470F16122317C3C84A7BA54B988E77952D1ECD012F90BC3C1E4152FD577E927847A8F943F5B87170A933C7DD5C8DEB4D0D02615E7B66D5CD399D0613CD111784C27A27A71516174B0D9534CFB51B6EE67D77767B818D79444F4FF519397E4C7B87EF06B0826DAE7031CFA63B835D4E2FC5035F3118463977458C6F5EB97F9CC87D77F107C28402F9D616681C108FF14C432315A368071A6F19C18108833137A4DF25764754FCF509EB6B1B118029CD7425566F4569020F5EEA7C3F5A6B0F3B62E8B615333F5FA43E15484C4F71496420D331D7FD74445816273113BBDA6DD5434DA9537EAD8479F20C2B4AE7648B9B04EAFA3A36143F0AB364552A482B766ABCB33274C832F5B3687B4802E3E369804275A0F443C5B76113E7380ADF03BF7057BAA575521D75F1EB5F774A7353A94D9BEB1BE70D174E602435FE55B8863CF5F71782B83F1AB62C222D5DDC8E04B20D5EDE755CB89E61855A160CBA7F60D708238267FE727DBDF67B8F160280A363F04B5EA62F79ECA260A0DA31B0C016DCF509A32E80A2B91176534A36B345FB2018C17540D14D569CAD14644F1135E779D360078F1D3DD4790757E035A4466B1C097751C01FC0E32D6A0C24ECD16D027441A38D26A9125F0D13039F9B2650E64C31322DE2743FB5306D057C1AE9B2051D916C0E076439070328344C970E47651B2D9E826DF12C6724347FCB3E13C61E0B79AD1E67D747C1BD3A9FF274B5EF0143126EEE3F6A72E10F75A70A77EF46B40B1A2D5A39BC2A2933DC3222787C60AC73ABBD110E73698E6A00C8BD031B626583832490B67647A3499A9431F3446DB4710E24F32F3F321E6C87427ADE28DEE2287D6120C6D633E1036A1B72067B955C3F873026E01957491B5F8D60888C13A91136FAAC24DF284668B31602C24EA4EC4BB0AF4A444C3AD22620E0B046E0CE3B28186265BD34C2EF34A74A56CFF15F230F308C6547D7252D340412F37F279B033C068961CA220A6855624EB860BEE53E8D0C492EA668A81164A87F042D9749DF8B493E4F2564D437ED53183ECB06961A09A3E30C4CDF37013A7435316DD6877CF3E2556689320CBF6B504205CDCF0B5F600EEF300669F65123B60972CA14785E4B54514143193B889D17415769413927235A24BA7D3828A844798179A44914C9F94B47701E07233E5B4050B6044270930583E5381AAD35E1C906952C1CAB143777C26208F90AAFBB6586295F168758400655DFB852BD314E5F5935735D66090521395B56CC60029B752D5D0E2B69F70043DA3A620D6A089661060B04E7402D33B77E726A49AC7C4D8EF52819E704B8B41745811774611BA3F108CD3143BE976A4A310D13320FB3C92B41C353C3725DB54B091B1E2CD9BB474360159BB615AD614399D7635465761748078FFB19828B72A1B57001CE20289E0F02BD15BB5E109FDE3E92BA495C660332337B94EC05568E4AE9065D1B0664CD4D11CA9A0BECDC7AD4041E2C044456B77E4E3C2066A33AAC412D53E66E2AFB12B946079B330BF0852D033342F2914337AE04425A02F80E1CC55C60530C08692008AFC758749726B5340B29F74A3F8F4045436293870A91746FAC025EF07C7D1D065B9D120962F87255E21E6EC73277D66B353D741F2D3ED099670C560D929D5E352D7CB5BB2A529F35FE8E514E4D72779256CBAF0B382A2E082E08294E6CC83955B2FD0ED1C65AC2355674823854F807EE707F83F42120375D4AEA21D076503CEC7DD539645B527856973BC0F06597081B7DCC48D428130A9F3230065632201FFA404FEFBB7EAAD60FA6AE57CE312CF7A51703610B6B9D0BDAD64A09D402C8D670DBEC4146816D80723CDF8E4BC6DA4CD5AA540FF510626F12FCA9485DEF65045257B5B24E896C647FD61C5D12013B3277DAD75B21243AFDA11109826B7B7F5873503323EC665D8C11E5CB441C4065FE1763BF302B21DF7FFBD021763F6031AA36369B198D761FD1FF1925583733EB325F845D754426B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD");
-        byte[] sk = Hex.decode("1C002800F0A040010E0AE60380A0400001000406101850408100020FFA0F98AF9F7E000701F22F08C060BE81F9FFFFDB1730208002FFF60F0070903E018006F917F8B72F60417FF9F1E7BB3FC0FF8100060000CCCF8FFF7FFD001412B8EF6FE03EFF04090A0CB0EFDF0003FF063A10C82F7F8002040604C89FF03F8082FCE9EBFF0F50C0000304FC0104E81FC0BEFD07F8FB03C04FE0C002FCF611DCFF6F5F4081FBF90128F0FF3F00010609D41710F080C004030AFCF397105F400203FB03FC3F80BFFDFA06FC032CE0AF6041FD07F4FD3F40EF5F817EFBF8F733D00F1FFF7FFAF81F00E8EFE03E85F40A06F407C07FC17EFBF70BA0E71FA1810103FCF90FD05F6000FE03F8F707F02F60FE0403FFE3EB5700FF7E7DFE081C08282081C08008F92D2C2030A0FE8103FEE5236000C07FFEFC0EE60FC00FE080FDF712FC078090A03E7CFC0C1640A01F5F81FB0D00E01B809FBF7E7FFD0508F0B78F20417E00FE01C4A73FC1BF8100F901F0F74F1F800208000014803F1F427BFA101A2C10D06080FEFC111AC4DF7F40C07CFF01120C581020C1FDFC07FE03286060FFFDFBFE173038801F828202FA07F417307F3E7D05FC1B50E8AFDFFF8306F005DC172040808004ECE5E33FB080418304FFFF27889FDF0103FCF5FBF73F0021FF8300FD152020B0FF817D04FD0D3C90E0FFBF080008E03FE8CF60C07A00F9E30788D01F8280FB10F22B686FC0407A00FDFF4B40F09F817E000F16D8BF4F4041FF0602E0D70FD07FFF8301020204B03F20828203F721BC47801F7F01FFFEE7FBD7FF1FC0FCFA0BDAF75F30DF000406FDFBF3DFDF7F3C7FFCF31F886F5FBF8181F904043840F040817FFDF80DA4A76FDF00020A06F02BE0BF9F0202FB03040058D09F037E0100E0FFFFBF803F7E0006FAC3BF1FA182FDFA0E16C0EF0F014182F5071440B0EF9F4183FEF909D4EF5FE0FFFFFE06F81B1840E08002FB0E0814103000007FFB13F403F8DFDF3F00FFED13E89FAFDFC0FE010DE4378850600002FE12F6335810DF410203F5F9CBD78F7F0080FF00E023E0EFE080040206DAD34710BF3D010204ECE7DFCF9F80FD02FC170490DF20FE010107EC0BD86F9E417F03F2274038201F7E7EFF050CF4671FC0C08100FE0B14F8FFFFFF82F9001838881F213FFEFD05E4F3077060BF7E04F9FBEBEF0F1F827DFEFB0BD8C77FC0C07DF600F42F980F60FFFEFC00F257E02F21FFFDFD01E653F0CFBF80FFFBF703E86FF01FBF81FA04FCFFD79F2040FEF9000A0410406042FC0606F4DB67BF5F0284070A0218C8BFBF0201FCF90D3838E0DF41FFFFF2E5FBAFBF1F40800300FC0380D0FFC181011024F47F0F6000810102120CE86F8181810200E0C7D7BFFFBFFEFEF8F1F3CF6FE0FE00FD011634F87F40C17FFC0DEEFBC73FFF3F060A01000818C03F408302FCF90F60209F7F7F06FDE7FFE73F60C005030EF69B476F600082FF1202F4F7DF7F3F0402F22D0080007FC0FCFDE607280820018001FD051EB4B76F204000FEFC21E49F8F1F7F0105EE0FF82F70BF81040500FA3FB00F60FFFE010BE013F04FBF7F7D0210F26F287FC0817C071302E81F20603F030119CAFB4F1060C0FDF9FCF717F00FA081FFFDF601DC1FD0607E83FE12E6EB0FD0E13E82FE00041840E040FEFF00F0190410DF203FFAFBF6F903E00FE03F8403FCF52FC00FE0BF7F02FCFDF39F4F40C381FA020AE417409F810004F1EFFBCFFF7FC07EFB070010809FA0407CFC010624A01FE1FF00FD08EC0BE81F003F84030F02083040FFBF7DFFF81B0CD04F60817F06030C0490C01F408108FC0F10A8BF3F3F7A041200FC1700A081FA03FA21D43730E0BE7FFDFA0BF447B0BF7F7EFBF51138F83F21BFFA0706FE1FA83F40C1000001D433E80FE0C07C0502F2070020C00003FBFBDBDFB7DF60FE82FC010AE4DFCFC0FE800A02E4BB3FE0DF7D0400F4FB0F402000007A03FE21CC3F1FE0417F03E8ED2FC0706001030402D62B38F01F0100010D0C1828B0DFC08401FF073CF01F404102FBF40F1020A03FBE81FF0000FC87CF808002FDF8EB0340A05F407F08F11B10F83FBF3F010001F013084020FF800101FC4300705E807E01EAF51360FF3F3FFE090A0C28D03FA00000FD07F617F8BFDF7FFAF9FD051028B09EC07F01F7F11F2800BE01FA09F20334C0FF3F017E06F9F92FA84F1FBE84F600FE2BD87F5FC08208F72D2430A06040FD02F5F10B20A0FE7F7DF906F4F7B73FE07FFD00F00510F04F3F00FE040702BCA760BF41FCFF0004F00F6040BE8100FDE907686000C1FFFFF8D7FF176020C0FD0000F017981FA080FFFF0204FC4F805E0102FB03D6EB0710DF007FFCFD0704D8AF1F01FEFE0AEAF73FC0FF3F80FB0D100CE8BF1F0004FD0714C00F40A040010002F2EB1FE0BFBE8100F11F08C84FC03F7E02F50F1C50B0DF82FEFEF9F55718C040C07BF8FFE90BD04F7F417F0114FC0378F0DF80FA02EFE91FA07F9F3E7CFC0BF24F906FDF3D01FEF709ECB75001FF800601D2D7FF9F3F80FF09010AE8AF7F80C081010FEA0350F0C0007EF8FDF70BA86FDF0001FA0E162428D0A03EFFFE07FA1B28E0DF807C050A082848F03F407DFCFCE9F33760C0FEFF00FF0B18B81F000102FD05FE53C8DF5F007B00FD191CB03F01807FF5FB07B0D72F403D83FFF807A8D7DF807D8001FA0B10889FBFC1FCFA08F0FF2FC09FBEFFFF07F8D74FD0DF3D03F300E4D337B03F827F0AF41104D0D01F807F02F4E7F7D7EFFF7FFDFF12FA3BF0EF7F81FEFD0006ECAFAF207F06F907F82FE8AFDFC07EFE0612F83F301FBF7C010124F84F50A0018204F9EF0F0820007EFFFC07EE2BC07FA080000005FA3BE0CFBFBF7F0705FACF07A0E0C07CFDF4D93BC8BFDF3E0102EDE52758F0808082FF0C0CF4BFD0803E0204F7FFF7173000BFFDFA13163470CFFE81FAFB04DA1B70E01F7F8003140830D8AF1F417F03022CE4CF9F60BF01FCF9E5EB4F1000BF7A080BDC0FC8FF1FC0830BF7E7E3E7DF200084FEF301D0DF2F00828201F2072818C0803EFF0300F4D77FD0DF3E80050306E40F60A0C101FFFDDD0B809F603DFB04001EE0AFFFDFFD81FBF1110CE0CFA07E7F00F807E04F903F000104FC17E03F501F007F0BFDD7E73F60C0FEFDFF03E41F987FC0807FFB0414E41740C07EFFFE0516EC2F9000C1010703E6EB675F000202FE00ECC31FD01EC17EF60028C49700FE3F01FF001C0490901FBF7C00F40700D86FDFBF7DFAF80360D04F9FFF8003B60E7FB7708849FEDB54F41A68314805A5C0766ACC9F338A46B29EAAC00087AD394D1695059DFF40AE256C5D5EDABFB69F5F40F37A588F50532CA408A8168AB1");
-        int smlen = 2881;
-        byte[] sm = Hex.decode("820713D8B1371DF090139185CCD8653129882FDC98C3FFE2E84BC7F2D80EBAA1ED2EF0270809C60D2AFA9E4250A237AA3432E612FC95427E40740031C6B8A6AFAC01A4D4430A8BF6A54F262C57E0235FBC4981EAC6514DDD8A92C551597E2E3C700C85C26FF62DEE5B72CE035F2B7457CE02556A6ACFDA6E985733E6062CC0AC1175DB2EC9D3D37F8984B315954C79EDEAB5B0B1432F6F25EC553971CB4DF7E012F19B9FDC5451EFD313FF71E8E97AB9EDE423A0F7D700F212B7A703735592298ACC16CA62BE985301E3B9C12D076E3C168A14F6FD5901934BED20199687B5236772A36596EEC99EDAD3362538FAC2CB46E0D1D546D112C90082307D41EB91AFEE0012E389D09ADAFAB94BC6C270087E769E9F1EF51B6F37E26F0329C925C87CFC661C65CC0CDDCB8955438DEC61BF2F0A1E644F4E8072DBDA1D3E03CB45C79B14DFBF8DFC9C220482B88AE60C1161B6D5B55E8B1126AD84C05C021A69408EC230ED2E377671E3E8662F57ED83D6C295E45A35B19E494BD75D0C2BC0FDC517D40B066E78A5F1FF99D948644A590998B6DE51BDCECEA27E13A118B8A33AB276563B203366F8790FF8FF6C617957FEF2873C32FA658D8F5C7D25F4665B25C73F7D4EF3CE91077285EB031ED4668A0692C3926E16AC928C4A331D8C79C8E93C2B93A2571D171B383E339608B3213462B20E56B6DFB14A6642CE0E2244C770EFFB68AB4D039AB34C172CA497F57F272030C376950157A32708E343F1AC11207254FE6A8F7CB958AD8934A5195C8D07378ABA0FAE7242CE322848BFB9B70EF852943E92A4AA8D604909DD0C3FFE9688B323E9126BD1D9E1365AE7243A0458C90269B565C09692C1FF6E72A5E687ADF21555427B77296AD64872F9BE9AE6556BF8A6FD9EF87BBD4C1D9D211598ECC04915387E3613A5530A3866B0084C25F3093796B92AC377F817E09E9E84B65AEE54E7D4C2D4E166F18ADB33A4F3886198A140BD7129722CEF4AC8B5E506DBDC4FF140F71B7B87B02410A25E5DBF25E94F8CA8E6E5DF3687CA1622F144E23A1859E59E45897002697B672D11D614AFCD9691607804AF9C8D7D165078265D9F16FDFCA92FA9AC36E6102BB5E4FBD25D771BB877A2105B03591D5A0805345440B1742DC9AB8979C09847EF3A92E40ADC3C6EE87A003955D688F0B99906185B9F4D31C34094AE8DA9189E828A235A7768900FAB1B192EC3A127586FBB1FBE7BFC7CEAE5738CA5654244CC0AE90ADCA3525F336CD557B293065D9457C1D64B400D1EC567863872F917E7D35CA192FD56A2119D3C3970512AAA3F20DF198E9D10C3938312461E2DBC93A2917BDD427DD92EA49CA4CA4BD2BBC1766AFE446805010683151817B73B26E71D48D9D11E5C5358CF5B366D6BC2206081C1603BCC6DE2429D0DD55DBD8244237A465DAC87184C366553320CFD4E5D14DB498D16A9CDCCC7029AB3F93CEBD090958CA3FD2062DD780B21705F77F7A6DD2517B3C2E23A950BCFD568C0D7AADB35CAB9FBABBF876E48819E1FCC137849F02FC9355BC9DB13CDB6C2AA9CF9EECB82AAB2A36F4105205EE3E9F098706ED2B5A2D7EA1A9378849D1B0690DD8898455E51DB42BF52EAA1005DD84476033995AD13A86DA7A2E388A929E0318F37C53C2D502DC0A4C21C88313726056F33039EF835F5491A2B0118C4263ACE4FABC611B0BEF2C355ABCA5E7DA1EFBD6D9CB570261B4EC5CA9F9BAC1AF7FB25DBF12D77D5BAE9EAEE0266000002F3BF104799157918F6227082E39317C7B48F93A9DCD2D5F6DB8F0D958E9F5DD9EB46E195366AAA188429BD99D33DC15E27047583DBD6A131B2BF98EE022003F124D8D6C82266236DBB4DDC44113784C7F821D58C75511AD9BEB9DEEDECC3DA9521895CAEB33660D9B95F66FA8A2C4048E7C1202E0A959E0CDB4E1D6FC304F7AA48B605C099824E7890B841589B49E91B0223F5B51862E5315D96E50348A74949F9F11AEE6C49C077DD119990F2F46676102738497B79E5959981A625A3CD5D8EF1086C6B7FB95F44B071CD18FA64052CA5E29062813ABAFE51063B94C6DB6DC70DF9E9B9C82F41B2C8E03B6BAAD9F5E484BC219AAD819D58F65C13BAB464DDA67C07D20E2F23ADA7610786FB20D8B6520DFD2535427C53FCCB3B66924322897DE74A10CFF1112FADE0B16D0012FEE2F852B3C8AA734CC413DFDEA0838C2F4821DC7EDD31D16C23AD7CA6B4E8E09AB764810BD741E0A7BA4A86A9F72B17CA2194C6BF62E315312F8D94B0D3D0F6D293D64F9D146DF8E08640E23855429D3F23D3860F63CEEE1E3F1B0E726F0DFFC74B494C1EBE52EA725089CE4F9F7B4F939B936F0592E5507C45B30C75844C97FAD4E446361347B38ACB22334EB7F79A6827754395C6EBFFAE5FABF096647C69D251829A50657E9B2A0EB455DCDAB45B52964C6339837D28BB76ADD4C44684C9D7FC8B3A559EBEF40EE5A2CC4DCA67BFF35DA1B736DE23AB3C994EE458A25B246A60BD602CF6428CBFA300320C1D78FF6498E8D2A504AAC0DC07E5A0D077445A78875F20275242F1D5290EB3B98171C272D4D686948D33CA4F3626D35C3C9376670AD9ACA8C420E5C43156A2CAB9D870E1D06EDD85E652D962BE186D96B2F9CD84585F67424D74004407C291DC1F454644B96E003BD562658D9B9C07807FCDDBEBE761223500B43792EA77AF20525EA0C1897368FC97097DB6CA4554D1AB78126EF40D6AE4A6961F8E90C4D3B10741B6D2AA3C89F0BC24985EEBDF691779BF1CC69B27E076424AC71DAF713295B6D6A620DBC1D6B9C8BE196CAC12E97970FF14A139D5AE21F9D802C695EC4AD957CB340760612894FD76C1537B0E87EE28B56B52A3276063A7E96E48C3108ECDFB2FF37BBD3E1B8AAB2D7407C91DFD7BB6F8CDBDEFBAACCC8D74E1E75B0298F0820DBB02EB372EB42EA768BEEEE93C891F2B768133D90A32D60FCC59BB9E62848F7D3C145ADBADA260461D0FE0D080990CF5C1DE9EC5104CDA10D6F1410A4693B5EED91E4F8275E86E18C2604374D1675EE902A5F8790BB55656ED228640E9043CFB7BDBC6E38850680F69FE51C60F6DB689911505F398B6991C19E5D9A04C4853749E9B5D90F8F627B47B9A490EB657E0576C587EF1D7A1E3005738BCB5503BA34B55888AC6D0E037D26463F827F67705860FADC524A50411E22B544988B15884DEC6FC463526FEF1459A1A47DEDA883B36FEE3C0915C42609B8B719D1E7D0AB1137D101FCBF22EDDD6A148D9EDB29A47128E13AE50F11B8AF5946532E85FD1F6F24D3084AD1F672EE9EF4C33C71AC6C7C8144589984E31A56E48CDE12B51007F4559C2DBD0031448E9A648F1A31AE84B9C457C59000AFE43576AEEFD34A8900CA19121C5B18BB8475F88967DA7AC8F5CDDC8F46B9D1DF7EFF843E65845AA03D3EF3AF35F6223B973DCC2367BE4CDE98464C51FE4556412664E1C506C86D9F691A494E70263CB841F1F0736AAA306F0A06673B6526CDE65478D7DB0FD9E4F49E7436D910FBF3BBD72FFA34823B83B78A7AB8754E9C6E76D0CC5033EC64391906A1B023949384F72FCCD85B4BE279FE5F69D04A6108CBC1706A49EC180EB6B9A93049F517CFC0E9EE45749889CD86C76B3E371BA5D9AF10E9F04911F6A46D734980A9B1D63C05C2276C5756FBB39015526008C3196FAA5C698B41F953045D0E366CA96DA5D46072B5AC836A2F87A5BF577453103EA9DB0816C9A46874DA2699777D071B27315C972C99E6453ABA11DECA1866E4296E451AAD416F1139118E4F544DBAC1993834CD5BF4CE3A83DDBF2177C2FE66BE72A13C93140DEAD9F9EBAAC1178B128D0A2726A91D3C07C388C40D1F000D251370E5AB73D5F429565A5B1431E9B4C0714E61F58C2918E73D381A38AC42181D3DEE092948A761232386CE273DD7B1F8B7C002CB8D9AFDA95CFC116A3DBABCEC7FA991726BE4AE4FE5D547C0B7206E9D53EF329BCF40A84C339C8394C8998B69ED6B372A4672DD79E068FBD7782CB3873526D43D8CCF4A4D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-
-        doTestKAT(PQCObjectIdentifiers.qTESLA_III_speed, pk, sk, seed, msg, sm);
-    }
 
     /*
     # qTesla-p-I
@@ -270,13 +159,17 @@
     public void testCatPIVector0()
         throws Exception
     {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
+        byte[] seed = Hex
+            .decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
+//        int mlen = 33;
         byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Hex.decode("EA7347183E405433EFF49CB63A9E736B39A86CB67125110ADF35536A44940BAAFAFAB19FCA5B8F11CF72F7199E051A9A607D75D093FD1DD7F7038F4EBB172F9549747FBEDB2EDCF24BD007610C111E032C1852E4A92A8EA33057606200B17C785B8BB0B2A08DBE93185F3B931371B256CEF871167BD21876B1D62FEF325BE2D0A716A6ACC3D3CCF516DCB267093035DA9359E8A698C3946D88200E9033AFA1E87A30A7A626056F944010A5CFD972399AB7C52FC0E87D255A589EE0D3E14C4BABCD5CA50A561E71428C98B72D413B117F827F0FB557559FB16FF4EBC73C539C43040ACD34FD676C1868F59F84AA1398A61E99C090A8E1A7C5A19CC23CD21BE2B9CA6E23F6A7E05E3E69AD6516A085D2C58FFA0883EEC30C04B648414C3D4DF1C87AE2C5F06CCEE989ABE455ADCABB6C84FD17D8A9D1158E91C4A2598A153595B4C2F0921ED24A49F758CCF7FC170B3959B2F26791572ED54AA80368D5800025F6138B595C7D8B6B8AB5F340399AAE0C821C4B0A3D677B530227B33525D9A48AEE067368A81305FFCB2E8615035CD56203EBBED33B5B1F5B575F920A627A770F0644610094EDA55FDB7C7BB7A723DFD4662780ADA0BA214D91AAA9C832FC29DB0B59EC14F4681FF05B6E5D087E91F891416CF8150604DF06BC7901B6ED33A5967E91799BAF9E903EB7967D393772F7C170D4ACFA36AA333BBE00AA2A40FB3A8D1EBE9D88926207677BC27BFEA91ACF49E009725D1CC9CA4566FDFEB762F4DADA0ED3323A5D48FE9D19B0B60809DE4182EBAAD5113019C9FB4F9216DAF2574EDE87B41E14590AF2B6E71950881DF07B5303C13EDD4D516FB059C7B7F1DC5CE6BEF6D2ABB65BC6B2D5A0BD555914BCF21FAB87D417F66C87C0F007DFC6DD6FDF22DB646F887037227A20AA1CDACE683FB7F9E4D4959FD9D361C073D22DBD89C4BA32CA1B9BA803BB8EAAE6495269030C24C08C8844427CA72F321303F112EAD94E3823D354730EF3CB2F561728227BDC2321FAAD7ED43B54603F94CDC42A06CFED0F44CF34F357CE3019973988AE101746513DC8054EE0464A8FA8654A724277C511D0EC41EA009F837116E9B51C9491E0D6E7D4796BBB84E854C6097156F8A77CC8913F3D05E8A478667071AC757557FA30958314499E377262C79EE8EF891201A7E13710F4F6B252EF970B9B71A050681CFB0B2147D6108F7415A55FF1E70CEEF738272B8EAFAE18907E6D64F6729F5EDA8FE2E779554B41846521DC530C3CFD36A60EC145D0FDB6AA61800A534D9853A6B364F48F54558DE0132A39B4E5BC1ABC5BF376A5BC02439498B4C21FA9C64A72A9A55E51078724BD235E3D75D8AA12D4186C2D8E65CFF70E93211D996556B67A0231CF46E80B1F258C8FADD5662BB8D23B84C8C5EEB2612B0BC2A7B432B321AF2253AB16EBA0D24CCBEBC6D9DD83DF22DEB57EDDE1677CA943FAEC92E560E58FA0009F7DC4673722DF8FD38299FBB34FB61239611DC97A6D24B1ECB4CD6DF5EC4658304D34EEC218A55876A338BFF8BB2470B7F8275DE34863B27D5DA1AFAC7A24039DC53ECB99079E0D7DA4E3A5632D97AB065D11EF17BED267CC88EAC42A9A7C119D1211724D0E3A68A54A6220CE471EC3CF49FA21B4541FBD0CB8257B6396E4F2CFC8BD0756D4C0703F5B5CD656268E98C043CE4ECF34A377B8846817DEB391CACC4050E797873A1F88E974106471944D8A2A25E80FD54024C95A2B5E496FFA076F7930EA29AED839AA58954E5228F2EF55420C048E3AD1EA894EBBD4851AAF0A8C99CACA3057C486A0E0C2F1763B4F3440C87BAA44705F563EFA435FC68D165D0544902A3FB3B84FA8D74001F4C314F106D4AF37BFC54191598D2A28869F46D76B68D72D5FF91AC1F0E31F556F2030C816EBADFC9F72A55B9CB5F9D4C881DDF35448140025651B9D0EAD4C59845119C8D3FDBFEB219142C3C6820032BFA7644DF7E7D2783E27E8786053B02EC68EB4CE6DF29BF9FA8B1BA2E8A6015419D0A3D491E884C4059184B9C6E7BC35908EA60BAF531EFEC1D2379389291BC5E17A31F1ADBB5AC09EB3EB845F3044D31706543F063644F6D5E7D6DC686750134C9811B4BC230EB1EF7E8B214D82E444B0D3EC367D5D5D0912B5D6EF40F3D36302D726D9A988C208567067B5C5D9521EAD36A23C8E9C956443141FF78569A8E0D9EA2BD815A3A9746CF4F1CE603E0894529948D873A67C8F244DC10D9B706E127D508C0A814E9132E1AAD7DEC1BFFFCBB98EC65F1BDD6A8A03DD08256EE50869EA3509C2797184BACAD88798492F8958327D5F1071A90F0698EEA7676B4A86DA0ECDF4F3EB5553599514CA78773CBDFC9F091124E62181C637793A98E02847143ADC41CB3987334566846E4D201CE926D23AD382CA2E5E153342E7AE8CDE9FDD76148251F09C7290ABCCA43037BEE98947BD673B1FE28C0951298BDC45317AF1896D0E4B91BDC2AC886D18DB6008D8565FDE10D5D87322A5777C66A910FA2710F83BF61B396E9F82C2D68FF4B524616261D444F7AE05B57B4A86E23C6B759504812C305906ADFB5C900360E79EF73F0BF0233F5181608ADD756B40EA15DBA12B4AF06B1952F3ACE3908F6AE4625202DB122B5087E7808963426B23CE9B85260C924A677812EFD1A55E08CE2ED673BA2D6DAC7222A336CA6F0A05996DA63269EC3F5BA62E24593472AD7818C1800DB5A727D0C8A492EEEB22AB981CB688853751DA34F34A6723591909E2197D1A4D065737AE476831C19F92953CB7F889EBAC46A31D247A9108B3450A8F3B4437D0208B0D21558AF4E31DA942CEA458DD88A605DBCF1136A2E47897CE17030984952ABC600B8F30292DA4274593CC71497E9BCEB9CF84986E4C2047BE8821F406F1E000E18F050E3DD8E7F3FBC923D0830D1A84DB2B08F8E0623E3480A20A48E20A2F0F37E144CA58283A97A20E12DBE95327D69CAE4234381457CBA8FA237F88C466E8DAA72BF89E9517C28BBF4744D3214EE8B3263DD64C06C78AD9D0CC42517048DAF4AAD330BE626F23338FB6819329D11C10D928C11FDB96BC2A00C269D7839DEF20580F82F7D9B48CE896C45F568A6B38F123BE6B0690722DE463690EAF8C8336F829D4D48E001A7DB63470A39430E6929BEA765B3E8127C4213658C6938CE5D566546528D60C8B938196BE5B3378755580816C8120803A46084BA1980D9F129D47462874022454DA5E040456B2F406218E315149D1B79F7115C81B7F98E1A1A3F61C96138152C0A1B3E124E630902E6033742F7C74946B5D2B4D08DFDFD29FF7479C313FE889B1CCDA14904E0CADD9963F21E564EC09202A1B263440E9CD8EF3C25F2F5D58D278DF530F3940B79D7AAD270497DB9EAB1536C2F59549CFED6E5F4A9AC9907E586357C7CA305170B622CC3A571E45E1891690EF11C1BE641A2E4169C2D037EF6130C7C70104D4BF844CDBBB6C805A9B210B853417F6E2E4C85CF4D6053DDCC94122D5F2CEBAB51E0232E0DE9B19FC5CE0B78D569884F4F2E33BC4B1BF6A370B7F2E317670A136FADB81873EA13178798FA48E9E1123DC5B31D88301054F0D964C3939C0CF1201DBE5B43C1CA9E95698AD80AF3EE0633F973665468039EB73C63FAC0E70DAFD81F17492B022E98C61950A5D50846FB707CB7CDC7C51BA2EB7B2648C22BDF81C17A8EBEAA351C396E2BAA27D014F16388F215C5DD5CF53D077A4BECD1F57D0BCE1CC415C52052934179366443F80EC1D2266A80EF5A4CC09BA0E2301219CB9DCD88F30639B0A5363CEC3847CDC0207FE65B260EC1A4B5D40E3870EBFC1C02F2F4591A4D666E2754A523D9F1A5E79B69FE553B8405E338E4587EE2D03336EC2675A95AE9A35D9FA2D91FF934D30D3FFE4C7635C2DDD2FBF852859AA91EF913263070E238AF16D140A470A8A4A2A38720BD667EEB08E3436EFC4B74A9CC9725A7F0ED64B55B084E8EA7EBD1ABCB8A21E03CEB0F9CE5E3445F226B5797613C030F4ED5C445DFDF886AA6D7230962548B53CB551F3E6CB2FFF4799AE47E2293AD0FB30AD70417BB9F12CE5E7CBF9835B593F8659D98840D5F93FD67E0C48268F770E13726687DDDF8FD41D5B729B1B800892BC46D46154449A230139E80B59BC827B7638E1DDB20C366583892B8FC40E5B038772E4055BB625996969CFBCD95374F5966B5CCCDE8EC98CA3170A43417A941FD1CD00348E12465568A3DBD075A08D282D84401553B658FC64E233C5D3E5BC383606C737A3581CD38CC7BC362A7E79D46CBAAC8A76C2093879E2EAD94A9E9CA4703ED884E56FCA8B4EFF34BA4A002C31C2A15258879610551BCDDC393C0E1E4CDB543BF3273C4AA541D107D3EAE2DC643993779E15D214B7FC318970FCC976C344ED4D147BC226D1F193A7026E949778E67B395B111F943D44CA98E5A4EED33272B13986D48902692325FF749F546A9E6674FD6B8F67901725B8A0543C81AAB10228ACF3AB5F7EB2CA870DB85A69E4E63153DA7F5F8D8CBCB254E2CF003E04362F4E94C6EAB08F53200158B8F3CC8E8333E2EC6D5A4DBB607D6B180D45CBF05A7A9A60FE9ABD902A2EF248B9CA9C516D5871C88AF24C5376A4B3DA19B96E454DC969BD4DFA81170C627A2F6A0F0D3D9C65BB03DC24C470944CE27E108F3402062614F878088945A201EAAE842C20AC99D743E650DAAC82970709EC2D1A6CD5A49A36C434496ED6F5E1558C0580B61312F63C195C4FE5CBF43BD8C3B189FD5F0766041E8DCF336C550EB66C4C0F2B9AA420F260D1458448D4AA5CCBE22340058D60BB0C7B21231ECB2AEB51CFAADD4D10B683248F8ED6D2B61D4D5C66EDA259B30101F71F1C19A60C987019F236C35259E7C39DE6709806D24BCACA2897CEC81BAF76205A488E6920A3792A0B906BA71B9B5095C85F6FC368ED0214F026A6342359CF05C768EF34F1CA5E1F30E76D57ABEF961EA7097B82F8B81B9219D9D5EAA4143F23FB4812CC8E8AA5C089F35E697F1FAE5F45B725560892EA7A99F9C8AFAB2ED9033262780AA97DFD88B0E810EF4ED113494B8B4556C46C287BED57E7F60E0FE2A93938C25F910ECCAAE27293243956F9D8E14F08FC0D6CF1E1F43E5FE5FEA8A1156C94DC94EAC01D61A47966CB7819AC17E8A6C9CB361F4B13DD43A61156D378EC627B0E7414A29BA7CB0300FF1EFA20CDC4A2D2CE25C628B5A6109F4542C23B6E2D3E27C808AD2B706399C23E392D350BF6D7C02F0926D9E40B2CDFB98971AA06DBE751854D8DA832824A68CDD67ADF855541F9B1004B48E921E69FFA23271AB2AC3F2F170035553B8A8A00C8C248E2D8A763A4C95A709A38C56622FB8F4E3694D5B923A2CD8E538F113E8A08DCF19655C3E5818586C334D4E09BC4703573D24DC77804B24EABCF3841F372CECC9954A56ECFCEE60431CB015C84FC6B61C11E15B5899A81D07EA453DE77551070EA5B484FD937CF5C3BA7E8380D38B474379AD0211E35281A4FF6A2291188853439D472E476B2E93FA44F707FB7BE136CA62AB3E9F02D2B864EE25B357621AC7C08AFFBE1B7D6C184428E7A59C0D5365E21DEA5328B183D540DA779A38CDF2B55B710A3B8181A853D04482829AA656C77B94DDEE372B77CAC11D8781F3C554D54C3A74583E630C3FF32063672E903DD4382A85E577043B0ED269D9A5523E1DD32974640BE7AE82E94A13906B19DC3521F1950E1A5FF054C593D413446B3646E738636878C5D2FC64BD602F7D731F7CFCBC297A979A4B2922972EFC28BFF3EA51CA87B504AB011FDECF3344216DC8C99921BBED2E697203B4E994E4F88CCD491329A4E15BA8D757E6804C0F13C0D37D9064B1648C435229CBA60E53A764D9CD4047FED34209F16005BA94D07E0C12E1C27C38406181C347096472AB7145FE3B24069CE1BAF274408FA690391B73CFA1CA56ECD497D5770E01BBB2B8BD424873CFDD8B3D1A28AEDD7DC4172A5489194529A880F1115AFEFF020224FFC7F1B199D7408D888F765CA6F75F4F000508A9707BEEC60E144572D8E13FEE95956968628003148332ECF06136A9504E4DFDAC8BB6BD4A1267809478E43D693A6E8ED6C104A05908A37C75087BDB5954CC6D0FABAA841B86F5DB6473134015F75D28CEC95E0CB87B0A687D37A704B9B7EA7BB9DEB76BA63DEEB2F0ACD7C1140AD02A7FF1422B3FB8CED19962738FFE453F561E908519451F884A015003E65D98D81A61CD8FD7DA2AD14BB4EB2E7FA80E28BFF59DCD12D9065B9549641FE599F41E582E1821C2CCAC0C4DBA6159E0BE7F0BB2847F2299EE23010B7114925C4AC8C2BBA87EDF43694A1824B08745686EA845C02A7BCE2D98F973D49500E79F563B8F0DC054C5F1D30E4826093BB5944D7095E662CF008628278D85C869A69B634AEA2735D4AD59CE0DAB044D6D2F94004A2B9C1591F2FB1F7F88ACD7F5AB252B7E72922287E4473014D26CC43AA3D631B6A0E98444AA8E31186F8A7922C3A6F911A3AD2F03F4372F143F89AF8B758DF20FF26255C31E5E8C3DA86816318AEE2DAAD6655A36CC36A375EDBDFDA241BE8DE13080163D59E6221A90299AF4D070D6658D8EEAA88553DDC03778EF9872DCAF19114F8D6D8B0C13ACB647C49FB0102D79A388148C52BAE905C9D787015463125B336193B495F16C8E35F2146F496EBF780C859EDFD95773B6A19E9E5E582220D2DADD167379F632D8AAD683CC9B1EAA77F141DA66B6F002339C6DAF929F67A57B14191A2829CC55EC5A940716F09FC5A9B08C23881D956A36E2BADFC84E5427A866E2E76B476B2A0DE1B9CD3FB6AE156C74A7689B08D25DCA0AEC4191E33CC091B181F8C67BFE2A5DD82EDFC226D49A1AC7BE8936C27DE05D243209276003A63875B43E20BDBF79D8CA81F7AC56AE94EF46CB3FC9BE5473DC0C9715BF8DAC744D093350B4C75560CB1C099681F0E440FD682A387E7AA70B3F335546F026C5351440A5D61E3C83BF96F07A46DE986A21F7E5A2EB0A28AAC2087C3896E933B708B4A44234EA8AE48946C6A10213521A553A020D7C9991CC31526A4E94D210F97E4216BD88504779F3EA21D52C2A205FA47CCD2405D26F411A9312D561D4CBCA0763311A0EBA45FA48CCE15C80419FE5CA04BF416395D25265280E0382BFDCC868C719874DDC795329E07CBF5C8FE83A3CCC4DDA4B31B15702AEBF561F53634315EC8B1A5423470DAB46963403E10EEC58E9E1C5C4DCB3935B518D4623E6C8C68A4FB13B492B155E06EE3D16BCE6C3E79AC891742AF3B8D01767542EB6B7BC8EC71579136ADE2BC39FD07AFA207C3D3012229267DCDF6CC3E9DB25AD378F836763BC517DDA13CD264C90983D73E2C9E2703740DB4C38773B49625967933F41FF0220F40C976385367675005FE20AECB7FBC1EF5C71255BEFE4A39CE59ADA83F148C5E4383BC65F0440A1F5826537EE40CD55862E7239ABFAA1D458F64F7423668EFE8E1E979B34EE18A71F82BD1975E09EE1613D9F45870B98B9855D28F3C00DA8B9740F50E0C51E506DF1C7C804FBD2A1DD912DEE604B2080069AFC5095D23D726D94552EB7986C14C86ADB5A1306C6B074F5D3605A6C2189C4D22E1C8795FDB77803C60E3CC04B82A021BB89BE2E3C45AC5B914966D4A3DDCBC32A760ED7BA6291B6C728F13AC684A6B6F0ECBEFC06B54249FDB3633A786E0822B1A2E43C3F59011263DA540FE2B4B5D31F5FE2A0954E45E106E1255EEFF02A717BB9061BDB528CBBB7854956824311A755B68EC1C179213C418AE21B7DCFFA9F9DFD8CAE58FA1D0B88670E5A25934E7F07916844AC026CD5E1F768978FC0F3AF91922014D75E50F4253A2C2E3420DB1856C206CA20651703D4AE01AE22B8C3DAC60B8961586986F17A93258ED44C83DA6276E04A9D0E03B04E93939FBBDBCAD6833960F02337AB1211E72BC180EED3DC605052CF86F31F1960DD8DD46E848584E909800DCF13146B6A71A79AA92F2BF8D254132423794FC2416A412B001C47A9875FA4B866868A9A88487FE410F81BE5236B13BCFE9302BA9443F3C4523CD3ACE7DE93CBEF6D9E4E6090214494027F065AA834CF24F6CBA9293A02934E52F20C1C2BC9609B559846B643BF49CAE7FB8332BB91B719E1F1838D7F63F5C3A7D5107431E3B854E03EBA795DE9C717B0C9D692B566138CB338C3E5C6F5931DC33214B74F3A116BED19B91E27572A5EB875F14EE81A8B5F2CA2C98739C93C04611335D221EE922C62F02E304BBE507E82E329B4359807F595D11BB5CF761619FF685034004A5ABC89BA4DE82FFAAD693221002F399A5F1D11302B68291F85079D10AA2D1C4536C70C7739CD16293EAD510CE3A04D91AB80D5BD2D02A93C6CF5BEF703A2273E628EA272987167F8A3E2F8E53C3FBC907C2A5AB051653051266470D74F14B632AFBEE256AB24CFC2C7F6E035A0580EC3D1DC11DB98C7FE605183884CF3D7F24C4B3A3D05A697F95601C367EDE314FD3D7CDCD618DB63BB7599E4577D6ED62E891CFD85C9B0E43F0CA588CF282A5E5070AF8E9CBEA9F1AA1FEC53CF411C3EC301967130267B769177AF5508DAFB62F5AD7D8AC9445C62E59612A54F77B1606BD9EEA24998726ACA7EBBCE63F73C143A56435B3EEFD63310A6DF908979DE38D04D7CC75B5FAD9548071D7D1647EC6AAD9663DB156622FBA093B1C470C535BA155FF88EB90BB7CAC9BF4CD367F8CF68CBC5129A577C31BAEFDA834CABB52E0D02FF1A44FF9E85BC88F4BE69DD5C1EB9D4A547395DCDF8DD82C1F98252266BA21395B4272A50420000A40A50CCB22546CB50617F2D7FCE96DA0DAA8FD668D3DF70F3639B45D25910B8970A6F26ADB6001DBEC9D78E94D020307793CAC8A4E69865CD8B7565765C4C4686B0941F7945095A4ECF808A46930AC66F0B569908E97E9557400D265AD10D2A4CDD023D421922BAF3DFD93D7120E4F2C4D20900BB32AE8242295B3EC9D61252BDCE14AC6EF5B2F73895DC27E5CDBD878F24369A533B33DD67ED239C61C333C9FD879D267446F1DEE3837B66C410E3AB8B8BC4C782CBD8449C7B2CC89E81C0CD0DBBC2DD6FE3AE7F2537DC3BE3E7EBCE49EB63A76FEE5ED0209650BF6E06344FE0A46693855AF3D87FC61DFA621D5CDCF0A39C80F2715392DF2C12685F37390982BAF96FF6475CB436DBC6DBCE4716781BE983393D197902C1C757B831BA1564149711D3D8231E908C8458AEFE45C3147A71F87E046193C6BF35FCCAABF592E48724C7E0582077FF94EF1DAE47D911BDBCC43876978BA38C7D6372B23DF650AD5F27C95BDBBA2352D492B5873C3835ECD142C5D3007A4D16FA35DEFD4C21F929A8B02ACB71CFCFB9540B489F9F0C2609CC2E26C5E85847D06E82B7E53B37C698659482300A18B9C42E962E9CDA8968ACAB266CF1340298693C87CC18AF9A52832449EAB89E84FE63BCBD45AC3F9BDAE65E62EE8F97F1165E40D874E9EF75763972A9413C81D18A1E551AE0208053B6276841F8470C23BF48F493BE5A0AA94AC10CB69F0E60267BDAE69EEE405312F8056A54C47A80CD01DC81EEABEBD8336FFEDBE19E8D66C566658D0F8EBE7999BFDD8FBC9BCCC59480F98B5701C8CA3156A94794ABFCF3132445832DC9A582AFC83BA2E41B6D64C8A1560880B7DA9976FB61F1C33ED52506570AC3BC65E345629A00A611A442928BD908A93CDDACC7D8D997ADC9AD9EDC836A803FB392593D03A79440D4F2F812B4C76CBD8F01D3B7119E42B98C64865EB2AA1626EF1B4C1877F6E484BC4D776C6C47712E88E9AF422D676103A71EB6CB31F360FC0EE898A6C28950A448D77CC07D513CB6A58D6364CB1480892DEEA7585CC9774A42F6AE49468A65A7215D4112C5B38AE6516798CA6A1281042F435AA4D6872A2AD8FD600638F0297B9C732E3D08AF5EAA9E24334E56C57C931B3D5AD37C5E5A5255B3038311A89C600944C0946008FA7F433EFDF3C60B8CB0083DCE9A46247E7B8C790E9ECCD03CAA10379C13D171B2C912927324A818D3EB762ED56B1B1C6EF068453A75B2C3757AED2D04333A11E21F12060EA5FE58B4649970A194E223CEC7B9D2BE63D57742317E93AA66F422E0AD7689878224E921CC95B53511069CA6C5492779864E3657CAE50AA46CD8BAE0B29D8DDEA69F05D41EEF627749BA606F1A614166D143923E0A43A1822D726613B24F4B7C691205F6C6F569EDC542228BB244A6A133D6A97BA66018F9AC0D79884B2623442536F17D9A3EDCBD00975A5890A34FE87C37B718A67A085A2B3579822D6673A3041BB61EE24072B5B67961B01DE05F348A63E15BFCB48D3EE991BE1699E11A06E02255F522BDBB1F74A5E90BC7F46FAA51EC84D1473BD47A9DDF9B0C226DB8602EF74D9D6A54E2685D6631EF6AC744F51BE6609B30BAA0DFADABAC90F6322C4D6D0CCE589F09A659FD2B4FCD7F70874FC1E32C7602D4A70EE1F1A0424445A3A2015185C80838391C80637B369F43896BB990A00D55D0C44CE10A5BF10C853E7702B9B03AEC1CFEB983ECEB22C22F199776CC7EC01DE1782D27359DE58A4B177B138D59934A2308E937F4A6EDDE167113E87001C6D485974DC7ACD95BD3A0C79436EF4A9305EAF894889CE7AA3FB758C2C98800EE51C37F4204C11716384A997FDD4F9CFDF71723B37B8D4F4B15E457126BB98ED49099F0881E0386F4CCBFB86F246AEA4A2790D1B62B6430E35271025CE8C710A1413D293959B8264F6C41FEA8EB007FFAF1FE0B57A0672F9ADB6734C2F8FBE051DE3B3E80670F8169DCFB30CB6EA4A7A9A7E56ECAFD79B4A8C7E35796F94E89A1795D9B7E19FC40253E27967338BC4883E7205E2D70765C38D8256BB2504BAB38274CD9645F3F9C65CDA32A50D09C1B7F98F0467D9FFE3A7385B6473EC2012F265D06BCD1522BC9B82019163632C63049D60C8F79DAD64BD92EA183B3FEDC67B450B88FFB825E0F3C36443394D6FD02D61D42881823D8E224FB0D93F68D39FE0C8AF40C08C7E5A438B9B686F3280B67CF7998532F53DFC01299C7732A69DE3076F28D14769A324620C5535EED84289AA55B06FFBF2BB4D73FA75AC8EB66609C5486559FCB920A2F4217F7FC25184A27D75B197F6C2A479D6900D1D56CFADAB446F002615736C9D541981928E08B4C4A75B6D9E0ADFA6795E374D71640EB096BD017BC16B9A6E71EFECABECC9E85040D9803C88A5F11F2DAFD49B506CDA6AE0EA5BBA9A627C0E89FA6B0D35FA493E4C853D728B1AB214EFE14E39A999E4872857F039C67024E106454328641577DBC7B74F441B01A613576BF0A21950B079937BC9427464CF4428CA5EC21AA34F55A0F42005845D62355FEA077B9E6111DD2402C715A972F8FA43C317713C71255D834588DBDB63B8F8C1DC5D672B346135E138D835B36334A38B0B1514FDD87EBB6D8FB8DC41D13DA74BE01A1823E417D3F932083FAD5F2FAA0B099DD03A002B5BDA0F1997B65B62E2097B948B406968CF7759AA75936CAA33B3279A1A92882A9C9A7109090E0FACF49790B1AD1BD606D551A806B1EF434D491AE50FCE2FD7B17BD605236E4E20616F7B7E27CA1C0592F25F3AA967EA41A73D72F4FE4F241610D4F7806E603A1A188EED49D4C7EB6F34CEC63F7E8B82F33B16D7CCE26CAC7151F4A7674667406941291FB0B6315CFEDCA1039B1B28C0E3F29FD3AD285B044C3C24D317A4D83D47985A2EB0F661AF5589F647D92554407AB5664187871F4D22869948A11E31E59ABCD8A14E314CC46D88FD8E2F4679C473B02C3FA0B5704BD2ABB65BD4917C3E6ED2CDB400CC4C82F9A7B9BC71C02E0AC7A029C159E894A88426E60599BE62F97223D12E318037A16DF3C2AB4F90A76F6594297CD5580DFC287D080AC03B445A9278F51C61E8D693123262014A2802201D92241C1269ABB4AD0ECC02FFBAE5AF52D2BA5AB08DF8CEF1D5116C809497279E7D4D9754AA5BD616BF0B5EFFD40195FD0BD17A180BCFE0585F8ADB1741F52DC34D403993C71519314F103FC207AED3F205DC546CD7996E2A2CD2712E26008A74B292A75A1011BA15E8D567BFC19A97C4359F5209A93600B81CA32A0CB1333BB535B699C0CE95496EA897292410D8C89B2FB9157002F82CCE582D62ADC159C08C85357FCAA0F9A10D137F09C5CB1F427725732BF1AE5D12C95045DC4B8F04BADF4A3BD6DA9D99E30D0C79C3FC49763883F77397D1908A74D84FC9BE14BE0AE638B4CB261FAC04EE77AA2BAA5745BCB4282ACBFEB4945EC820CF507B834C70DB6ADAD6A3E5D0796B375B6A29B42D7564EF51E43943C708FA28C4F565D5867FD7E6260D44FCDBB491B4F1394120002271EAD1C1DEC08CE3E942EB8D681D837EF9387E960B33762B59459CC1812E8A847F7EAD81E0220F4F0A4896DAE94C2C6967EC578AA4D56D0D7CF72C53D17D57DD00079D10CF2E98282C2060B483735BFEF6197619182D592E3D9D7540929DEA260D6BEE7F38A21C267AD0B7194A1FD37082117529F2DBC64A39E170343ED8B8717796D802A57B57975A1232ECD48C79D2713EC1EF8F6FC731118B78F081B680CCB79A0A4232DE73D1EACA3455AF0331921195A8E70DA28D8BD04C178198D55D6DF1C5A8C1B641C26FCE25270A6D07D2C1B52A824A2C089988C35191A98A6085EF3A000D22452AD2C55DB0B87CF00C6BF2AB7E66E341C03BAEA55899AC814DC988391250A4D666A9F98F4A1CB16C80B23C4A97309AD0D42EA85A2776D3F097587648CE0564826CE4127DD1E7314EA9A9CF4910E17EBDCC40B4CEC74647B7271C3B735DC5AE8E958CC30617F01C3BD61B5515C91C75B98EFD8DF09D8BA8DBD513C5CCDBF7C5152C340500B09EDFBA42EBCA260DA419FE8856B4B1F904782815261B3924A5F9347673C02CA252B666D6498518A79682B321A8BCF2514CFE07E387DE3CCA6F92AFD4C31D2E83381A1FA741F5760845202EDFBB55582D3CFF2FF2B04A7D469825F1976CD2A8C388FF958F88B4E52C5916B30E6FE03BB9FF6D721ABAC2E7EC46CF6DAC3362929F00321FBB0BA622E1E87A279D5B01D43326D4AD95574CCB2B77E5C8F5E7E9670F4F1EAC11DC3716E070A8BA20E1FFB4FDBB0B75FDACEEC6E4C426C31CDCFA95DF4A1E4E6FCA2D6D674E641CD707281B9D5EDB5DEF9AC42A212A1DCC342DE5C1057E49763A791EE7635F8194533BB2541A690688CAC2293FF19F26B12E2CAAD572B616DCD9058918866155347FC3B7DCFC5A9FD84A9E020126E57CB6DBB5C3788D8B20C9D00EA68DCB344988BC7D92AC98E3E6A278CB536F0AD1A0D0DD8A60D2EE9775347EA0654566734F3687FC669D270A4FD2574770740E1E85D8453E4C7A8A97BEB84F229A630632B1CC90857FE2666E08FB105AAEC690B22B9035766D5A2ECDD63326AF9B48A3247D7F4007947B9BEC525611223B0957532B818B5A686A027FC0C5DB24A849EB573CCC55C204A2EA6927CB1E811F17E4E23D11399C3F88874B79B455D9470FB47833B10258B6739C120533FCC0A64E97B87DFFAABC3982A62DAD557F01F926B2D01A44DA5845BAB6C784D1E226539663F9AB7C59FAC757CF8C930D0BA7CC74CD98B7A60C755BF63B2362F854BD0D4327C6829E53421122BE8E46C54687958B142857039E565DABBC660F289F40E3036E5FCB332644C9BFE40F88DD932FAAF338188D4B7DE60C3D9C2153BD024ECC1BF3E7B653A3704A14B7EA6437B03FF83DBFB25413528AE9D4E8BACE91B93323BF6006A982D34981AD395D3E52BD33337D02A8370C088C2616B9CFA71CEA8B1AC23D3053F85C4B4B195B7F1F7B2BC11D0CD5FB5FA4FA64D95D14F0FBC26873F31703E15A74EA25CDACE3D8AD595A2EEFF6385406A2CD9F182C24B05D842DA26AD7CB762C76E5B255803BC6A281243700ED74093B6974B88EF179FE09D70483CDE0282E26EE377D526B088CAE8B72AF4D82DC178D71FD05BA2DDF7A912602EDB6153F45E3250571D1CA84D49E543B3717DE7E876D19C543F0A0EB8AF9774A6E2E7A17C679DCF2345D4775F292B150FD5789DEAD85DD1536E5FBD9D3445FD5A78FD7A9761A8D588A8CC97720523513DED7D6E95CDF640C7F7D0667B3A09134571A2E741DF8A007EA56765AE6EC046BEE94439E675BB49B6E32EA14F56AA3FAB2AA45FEAF65938EFA7D83934B5487896152F7C0409BA2572C18C7EE4EF61A5484963586E10F709A1396AA2565DCD9C0B4736B56D205851D2452CEC5E324FA4CB6B8698EAC2C9CE9074A17558F23FB7BC14868B8E73B424C8CABFADCD4C5A3EBB6903CEB7543FFCC5585B85BFFA94DD2AA324C505D7F08EDBCC0024DA4E5C1671B6CE012E605AA6B1BFD58D92E8353F05DA5893E3D3822D76338A4D8013297C5F643D648A53F62E8920674B58675C64D0C662FA08FBBC1E9FBC088FCA457E14608CB0D0272A4C77735BA3A7A26D1DB6DD2D0845178F4FE627EBA84ABD7679B2F86E3D9AF2171927FC74104A9F198733FDECEE23D2D51431641F657A85D63244E96D2636096315F291C019197E73C9E11B5C5A015C12A43D176F6D33E34D9C423C1050545E4CB89DAB3E6A58A8E2FF3415B4831A91E72718E5D09832C4970D3C3D600CAE926FF8A24ED9D14A5E5A623C7A777639C145D306F20A9A5B4DB24C99F824F40B5BF387A0E1B6B49468A62755CC437E787DF96748EA3FA5E537922307E7C0694699D227350BA020A5DECF680599AD95F00C0D73F50B13E8385C04BA03C0F52BEED8F4C1C1D9132A3B9608AA1039169A8FA850DDB4CF01966CF881C64EBFD758CFB245B486FE5689C20FDA833F67DDDAD4CFE4638BFC7F6D967F039786A330D8393EAC7A52150DD7D5D30F79A09CFA383A5B7E96E3B2EB6BBD66CB3D6D844B0BEE333E06D027E4379259465A38516686255550A6074B1814100A60F84BBC3CE560AAD08B21821CE740D90EF12452BD53516DC90E60A4480CA758401660FE1A6EE8A88B433C14698CCC86C4A270338DDEA2DE2A5ABBE1C5CDA9F2C222590AC705A0C86F927D830EE91FD1A3B9ADF35C4EA05915FAB0F988D17C522971B7FD2431088020CABA03543C1E76255045A198EF900099FF2A982D626A4DD09F0E3B573E0660E2B09A8C0E7000DDCFAD466E890A4746D92AC1877A53215E35B9D3EE718B92A14706FBBC87B18A42D91CD4017AF5C6D2F549A052A09B57CBF186D1E9C004624D4A3A7A8A03A89C47353EACFB90647B2C6B29F73C506835E320DB751A1EAD0538B74DFD39B383FD7042B51CD82CF10DCE0C984AE75C12DE1A5BD614045DC91989095D32250EB1137827EC5E0FF91D5AE8807389D238AC331185DDC332C90CC9EE4394FD9EB81CDF04DE8D22F6856C5BEE1B4E5739FFB19CC8A948D24D764947FCBC99218B4DA2C439DC410BE7262C062486D55AEF977C09A36D9262E2BAA088383422BAAA20F4D8F86C3A4D8422851B6343AA15C4A1DD5EF0ACDE63937BE7D7F7444286BF00621CB279F49B47835925D71E07FCFED911E94D378A73548539CEAA7D8D84EEF4BE67EADF7C7B57452CDC106033D2B22DBB25D05856C4617E144F4E23094ED4D2BCB7A5DD1DEDD661432CD2AA369EC0B32169D52314A8646699B49DA27FC6C233C449884F540068127850D6E383DA6CA607A0806561363928FBB259AB5D98ECCF00B697E1810CB5422AC9B2F12AF205CB3721B5FE1680BE7C8E9B89A6EEE126E75866EB44B48B3DB48CA63818E6EB691002B2130B6C591F2605152E382251153A042708781773146D57C7B16D62D5FCC2252CCDCFF10EFFA1BCF2D4305C34E4F19F07595102B0A23929CC5470B9E16060DFB7D0F28956492A5DCC8825FAB6133D490BE3BCA7141F049A52548BB792E8A84B0C6A2F7B13196BB8C27DBCDAE620019EC8E480E6521393C98866B04863B8736039F42C23CA001E3B83178E1A65FAC153CC2F32EAD32440FF6A8A4AC29762903B32140B49BB59E591B6480BACEDEC7CDC77D89C82F60E23B5B127B72B9A271996E2C9FE310346D739045EDC020668D69D7EFA248FD6F786FDC4A5199685D68084A1B4ECB0719A8781EA366B630F0C18D023F57CB302D92F8153FB99ACA4EC2E16C943FCB944650EF9733C97F66F986945B8531B65CA531798CA139D8A31A1CC2657515B0FF4235F0EA527E0EFF2E83B9F160857956C894E60AD59345C95D2319CA82ED49306307B75800036D0CFE6689BF9672F9D065678861F7E174489B789FAC5D6942A792ABAC3D85359C36D35286A51DB42C022B305D182E65A9D11602B6D2E88AEC5A556EDF3979E1660F162409FA90C3FC1377370C9389CDCB305772A225671345D30055A528F27AAA77819A7819F64AD7185202827EBE0D92A1E30B05BC17F45E8422E2A0DFCD3BF1347841339842F908E30502CEDFFBDBF01E2A1CA4C8F8BFD8489FB6A60F1EA4A2BEA49A9F986A9BFE1792F97D0842B74AFAD6E97902FA043A087F2DF276C5D30DBB274A824208EEC77AF7B84B0D4D18DB495E976EE802AD61672EACD514F7CBD355FC7F11673D9198447BD142B6AC1A6225851BA2D7CD3565CBD3D8A7341915B40F713165D6B4B949140B91AF9F89E46FD45CE4ECCB2C259BAC0DE91ACC11442A8C146623A0A64ABA992816FBCE99C835554B6ECA8C53A04CF70E81067BA2854FD2332E9A3813F9A3F1E039D4CE9F349FC1BF9327492DA648740E5ADB7E470B513B9375D439FADDB93A6B928440A31FF6F1184C33E1342CD3B29AD04C22AA2D68F4F3613450B7CDB2A3590CF60C03401949AEA3AF2060047A25F37B6B4D81783A6E3BEB0E6F3EDF523667F25D6093FFC23C0B38C99DFEAA9A6EECA7A7322ACA8417F80F819CE53A3090811733E71EA07B5D3773553335EA7BDED1128D48087FCC05E5860546186B657233B4A370B26969D7E179C49BE552B0D17DB34537645DA06F6B1E40A897FA632D1623303671A20F38971357C139602A03BD170783F6F061FA433986AE9747F3D235B662D29D1DB7F077004E34133EEBA14B2C271F3C160AB1FEA528C85404EF08A33A23256BB47A0E55AFF01ADB49845AF72057F2B9FCC6C9927EC5EFC0B510D65D81A123D082B3AA20CBAAA1301C425CA97CD8EBB2FC9D0DFA17C8F0010343BE3E4B2764A269C598B8E541E02295707B6D30968C75B5EF76A1E27B6C5190B818AB5D543E3E8525D007AE95B948B53E81E96D349041DD97AC579ABC59FE1AC5302691FA88F8453D90BA87796491EDE09F70C67422AE3BA6A5914219321E3EE34DD59379E412009869E6A611D4E0CFC7A56D645AF62E130779438BB89CA54BD04ECF012C94B59A800987B449BA086C859C91F1FF5D4C2C5A558342B2B836DD427D45C07D8789EBAD66C2445F5931EFCDD4EEC651EE22081953C30F7DFFBE1B98A8295A545F829C256060A36849FD80D4BEA694A404B4252D86496521EA0EF4BA1415BE3C3A4FE687E67E715D15C7DA44CD7FA18A010F46E8F9221466CE53503D3A9840FB77D7765B84D5D4CA19223CCB22FE81BE7215C8DD0E49C84D787FA921456A6943C5892539062313FE024A0C2E24D15D75A8097D0D393C422099BED9259878E4EA02ECF174CA3B8F5C80D01E998487123C10EC2E04043A4D87154BEB2087C7194F74201BDC8FAE3D8F202EE83A21DF9E5B3B0908C9321F4431CF108444CF5A93175B4563B253F84897E3F720E8EE87E6B268DCABA7017BB07C5DD29133B5878B6CBEF6DDE103E3E34D8BF6DF151A3A24C077D8529BCD02681DC9E5701F93939B5AAC45FACE46A5855E4B4C3AF82C303999937C48CF3D88734EAF75E3EAF171768F6F48FE2CD5FA9BF40328E642D2510E331BE5C3AD6632875A9BD51D6904B95BD5C17B24C43971D45055C7AC0299017249A936BA40E3BD17B63B510F4B7A3B4F236BC073EEF4F61CE561D893AB5B0A720D70AC138D47CCD5426C827E1AB13B2C140FD6E6903EEAE7B30A06851759FDAA28810A258306160452486199EDD0F2B44F32361E8EDD4A458A93012DDB8B4C08A565DC49EC6BAFCC759237AE69DA0DA41251D9E7E8509E4A09A8917418C6B2705CF7C463CFE64A67F88749CCB80B8B1B20A33B5D9DC3F1E74BAE981AC670EE2D86D7B4C8E891F3D5C98B3F5455423412C641C0315F27A263DE975D8D6DE1F17D6FA51262D8E6B3F32AFD9EFB72958460B5D58F4BB5CBF5AE1CFF2D2835FA294760854FF53522E2D52981FC276793EE5EDA989EDC921CCDC50D4B63A908BF47587E4FAAE3A8A1BCE3A21E331D9230B6B1C4CEC7743AEBB83EE003690D2CCB27301D88518A43B266A2E2DA78742230B2258E5D1855A990827B3A8B900362791AE617A3DC0BA2D12766A70316229028E09F96EAD7C773158A549AD9E500751D812D3F759468E22B86FF85DFB0C7C79148CE152A813D0B2D3979A3DBE3765102204ECDA68D24FA6330D599481516603607FA04439B9A0498BCB359F1EE20D65A5A84816AD43750AD1838EF4597876291C98E82E1F46A6963C7726C89470E778BD0A65CAD07E2A167AF6067CD058152E9C14979152103A7235DCD0755BDD793F9B23AE031133756EE3448CD1F93985B40EBE72F7F54124838E3787182147B84C3B3BF8CA901253E04BF808E3F3AEB84C05AB5CCA8D0C755D7B0CB02BF9E06F3113FFC098557DA3F439D397251D48D2FADEBC40286A17C66E0A8533328DB4DFCE9BE32F115A293567E4EA004157045D6996C7B73F27ED2F25536C2A381996257FD23EE4D962B663940C2A57442F5A7CB41B4E6615B223E91B55A7F98B416B2A6003D3A27B496F5E1A446DA9F0DED6A5AA0D7C283F34DEF97065049150B136B2D0F7A4207C904D836EB1E35BCA84EF2780068ACEF1942C2B31BF1F89CF218044A30CED4AF8743EF034D7F526989B99390D79D7454961C4606F6AD659487A803F4CF5FDA4F0A039664F3EA84AC4D7E0E805759A79B959906655FFE28609963BD8BEF5C82A50922A9836ADB18C9416358C092D69292C40FB3BA288D4029FC6B200E59C7C2FC9423360B11F157E5286D0A796A74A088E257F00954CA0789E3507E572EF3DBA68E3CDC7AE328F2072BEABC0E901D5F5DB1A1F41D14732F471F089FEA9D022660949C80D350B37B7F2F551DC22F665DE00FDA868F131E30C110CCB4C746663513E9DA22E143796EE2E241CDC47E2BBC148A230157C0378193F75248FE64858899B3B357CDC21CE8A6C79D2438569CDBE5A44015038C0CB8C260EA75698C272C9D5B169A4D2FDA21AAAB502DE3C4609707BA7C1B2E118F3F5B8A17DEE019465C6EA6B098139BCA2E6873D9945791F7D5488EDC2FD08E09973A55415ECC21556C422F2E0057387363A3D762FE41B054D3C5ED107C4628C15A409C0ECF77AA5AB911A7D896C9D106DB44720A42E0F89447B9E8D277859C941A5E49F4A577C0B6B0049972DBEA98A1E57991F6C0D239BA052CF369D94CEA0D2AC4A0F178D0652F9E028E12423D8D14D7C8ACE33F7E9FA9C2F8B987F89B1BC22190146FBF99B124D028A5AC57D4B21A8F2A2C9BF1F67841BCF3F47051EBD205822DD7F13AEF2D9EEC54D465D89C987851630D0F7930B4451914393C31468126AE0C607ADF304433E581A71C5A19356231A9C7D9810D282F3DBE109F6789BB997BB73E97650844F62FA1954D9BA9C227A23A65F500AB459AEC13D0D25DAC9DC885A01912C70005F440B0EB1A2C0FAD96C7D597DA59AD0199F7978D8DC871BCD4B8143420F5F9900600FEEF0D8C908CD259AAF1D446841B2F76827CE888E1B8A2C2C2FA63AA0C63764E173FF170526FA3D7973931E6C1317F9E7175BEC6F2D6D9FB36730A604879CE7B2FF5350D1A689A57C5D366EA0C219A8E839838A0FCF45A2ED7FD08AEFD2BEC15C47391D9D18E42C18F03B068DE6FC160C176499D06ADBBAD50F1773D036CE5AAA26247B4ED0B68415D4CD101C8B97567290CE4068F4CAE9A778F996A3831FFEF44CA682100850BB5B6290AD8CAA7C10878352C707C0E7D0D0D90462DD46C6317755E4151FAF1755106983B75B6F0A3FCB923D56BE32573D59062008A48AD6A4A87D6242FB13DCC2A7354D470633D292F3EC598A24AF75362BF3A4096C7400A1E97E31FFD716D75BDE945F8FE5A2A40C8C557A5181E7C4CF155F0E1F168A0F25F3FBD0D24C743977C12F1B6D0B4CA5042017194AC81EDE63A324CFE149482AEB04CE5209F0BD768C7CE90508DFBF5EA9A66A5A6FE038746852359919DDC76795A68A4B522C86D5CFDBA46BBE140DCC5B377F70FFD7B776AD3A7885C43A9A7B5BCE599415FCAEB0CA7EBC1D51573F95822CB3135B602ADF40AEDE049501F6BA26EFC18D4A5E9814EA2C2C4CF301AF331672896A9B383F7E582F3A09BAE7BBCB9AB088A0D7FE43827C393E5EC27F08BC5E85C4705EDDA2862BEC9A4DD0ED9BC56D592F6062F75719ADB6A9CF1C8C2526D6B79E4F53932AEF6031D30498587788B0569F6C0FB226105FE5CA0B8B5239C3D11C795674DFE2C8B46FE16DFE5C389888775E56BD14DAE9985960D0817E5354FAE5F20147B30001A9E8D89E5BDEC5567561A27A08A84E2DEF8182060C003A8CF5DD667AC830497A9D4BD1D003019630FCA28C1E272C8C2EADB1DBECEF8E2CAB9B60BAFDFBD6D75562294E0E169506FACDFDC559581B86559720FB517AD8A010D64D03ADDD2CF9CFE8D2FD244A4378DE585F2DC009B2ED6DAC2FEC87D6097A25CC720E7DC989332C5A341503B7D03CC57A875A1A26DBA88D840F24EF8C86DBBEF37905CBA382A8A5C9980504D0CD6CEBCF00F7335705BFAFDE3C3F82EB3225E5EC63D6D69EFD015C9C51AF71B23215E4BBA667AD525852CA6C29AE3AB303AFDE0279C45668D02A2AA96625FA7B41B268E783F829A3A2464F22436917940E6EB3B1D313F794BAD2514B35954718CB2D92CD7865EBA847D388F046CC7D5D3E3B57D12714E9BFD4A09A18A8A626CDDBB21FB599EBE030CED022E16D6C65085EFC6911F7D154B8C9787D3C4107110809219414377EB07AF5441DFAF02EED0B6C7A6BAB1D9902D7A3211F22536AEC7A0A365BAC29BF69021B46EC80143CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E");
-        byte[] sk = Hex.decode("FFEB02F6010609070CF80CFCFD01F7FBFBFE02010004FFF002020900F2020602FC090000FEFC12F6F6F9F601F6F904F9030101FE04FF020203010B0706FEFFF7040A040900F8050805FDF3FE070D060C0A00FE0CFE0D0D02FBFE0B04EFF90002F7FDFF030307F303FFF8F5F4F60505F9150304FD0AFFFA1500FBF409F801FEF909F3F3010201FCF90B17FDFD0101FC00F002F000FB08050EFEFD06F504F5F906EDFEFEF3FB07FB01FC0106FB0304FBF6080005FDF605FA00FB07FAFFFA07F8FAFF050810EC0001FDF107F9F2EC03F408ED06FFF70009F80C06F60102F6FBF4FB010207FB0A0304F9F6F90C02F80007F30D0C04F5FA0702FDFDFBFE0607FFF0FFF803F30D07FBFB010300FEF9FEFB0A13F007030A06FCFE03FD00FEFCFFFF070B1004F8FAFFFC0D0605FE0306F803FCF30506FD0211FD060507F8FCEF0CFE00FBF80DF20A0F02F8080011FD01EEFDECFDFBFDF5F8F4011210FF0403F8F3EB0D050212F3F80609FFF903F5F309F8FEFF0AFCF6000EF80BF6FEFA0C01F4F3F2F508FA02F8FF0A060009F9F70EFD04FB060005F3FD02FE01FC0DF30AF706090CFC0006FDF80AFD03FFF80BFA0CF30B070B0B07FC1003FB1404F5EC0709030EF701F705FB00FAF2F500000A0406FF08F5F202F8150F070BFFFEFA0603F9040303FC0206F8FCFDF6030507FF03F4070409080C1106F107F20AFAF3FEFF070CFCFFF4F5F10710FDFC070EF7F803FE060103FFF4F6F50D0B0202EFF30CFAF5F80C05FEFE070802F8FCFCFD02F81B100404080213F8F90C04FFFA080AFCFBFAF903F600FB00F60503FB0106F2FEE80003FBF9FFFD14FE04F50C03F70103F2FE00F5FAF600FDEF03F70AF70604FAECFBF70EFF01FAFE080803F20F0D1704FC08ED00F304F904FA020403F207FE0DFDFA040B1006F7F9EF06FCF909F903F7FD06F602080501FDF9FAF40106FA07FB0CF50C07FF12F702080A0900FDFFF8040A0BF20D04FD0F00F8F8FBFC00FAF0F91705F901EAFCFE0304FBF7F6FF0E0802FF0FFC020B02FC0801FE03F2FEFCFBF7FFEFFBF503F8F707000612FD0605040404F7F3F9F6040B01000009F5F901050202FCFCFA03FD1501F6F104000DFBF7000DFF0EFFFE0000030804F1FDF313F403FCFEF7090E0708F702FE0C040BFE0812FBFE0EFB0311F7FBFF00FC01F8F20405F502F40008F8F70FF705FA06E8FD09F8FBF2FB0704FCF4F3F5F9FE0AFFFF05020D06F4020413F500F703E7FFE30401FB04F9EFF407F9FF04000EFDFF0707EF0509FB03F506F9FCFC0400FF0900F3050AF50CEAF7030905EEF50C06FDF8FBFDFBF40400F902FC0300F6FFFDF7FCF1F014FB03FB09FE15FCFD02FAFF07FA05FE01070E0402FDF31102FE010802F902F4F90AF60408F9FBFCFBF503140FFB0DF80B0DFAF9FBFC0102FC06FA03FDFC04F90DFEFB0301F3010C0FFE0FF90207F80703FEEDFF0A000407F1F8000807F7051306FA090800FE0EF4F6F6080AF503F112F106FCFCFEFD0BFE020CF40DFEFFFE0F01F6FF08FE03F40B02F50406F401020301ED01080508F70907FA02FDFEFC030108FE08F708F6FBFD02FFFF0000FBF9F7030505F6060809000BF8FB00FC02FFEAFAFB030309F806020109070DFC030104FDF701F3030518F9FD0800F9F60DFCFF04F8F41108FF0202FE03F6FC02FCF706FCFDFE08FAFB04FA0B01FAFA080209FB0CF1050C0EFB03FE02FEF80CFFFD150803F7FDEB09F8EEF1F2EFFDFBFAFD0A08FEF7ED06FDF4FC03F7FAFBEF07FF06050A0307F710FB04F5FD0100E901FE09F0EBF6F203FBFCFBFA0008FE02FF0A00FA16FBFFF70BF80FEDFB0000EF01FEF900F9140C0E0901010606FDF2F4FC0FFF0EFF0EF8FF06F9080403FC04080BF7FA01F4040705FEFEFC0203010C090AF1FC01FC06FB01ECF3000B010E08EDF0FEF9FBF4FB0DF506F4F007F6FCF3030D0A04FBF9FDFA00000D010C00FBF0010100EAF7030DFFF608FCFEFB0207F601FCF80109020010FF100702F9EFF800F40109FCFE08F8FDF508F2F6FBF6010EFC02FE0101020D030BFCFBF2ED01F8010210080EFCFA000B120017FF051711FF03FAFBFB17F805FAF9FB0102FD09000CF90B1A0AF500FCF60EF7FCFC09F1050DFAF9FEFAFEF7F50CEBF5F8F3FAFCF6F6FAF308F6F505F6F9060A00EC05020AFAFDF9F50200FA0306F707F9FC150A000D0702070B03FAF5FE0EF7160D06FF050004F9FCFB05010107F2F50103F60305030610FF0B070A03F704F602EEF411FFF9F5FBF2F5F00101FE080BF303FB090402F30509031107020102FDF502F5030301F2F8FCFC07EE0A04F4FE030406040802F406F4060FF2020DFDF803FE01FC030AFF06F50A00FDFB12FB00F4EC04FB000B05FFFFF60EEE02FFFFF7F301FC0CFC08FB06EE06F8F9FAFCF5F30BEF01F4FE090400FDFF01F8F20BFCF40D0001F708F20304F906F20201FA0BFBF4FDF309060BF6FCFE1007ECF30202F907FEFA0AFBFB05FDF60BFEF5070CFCF60C00F5FB0AF909F1F40302F90D000503010705EDFFFE0103FC1004090C0007F80012010DEBF3ECFE09000807EFEF09010EF202FD0A04FFFBFFFD02F3010906F808030409F9FC0D010C0200060703040606F407FF0006FE05FBFA00FB0FF6FEF9F8091105EF09010300051304070DF80702FF06F30A01000102F7FF0EFD010500EEFBF802F8F6F9FC0602FE100105FBFFF7F8F00AFCF906FF0401FE02050A0205FB03140B040806FAF900F5F3F906F90FF9F604F602F705FFF8FE0A020C09FF08FE0FFDF6F3F6F2070002F8FEF1F90709000301F307EEFCFB06F9F7FBFEF9F50EFDFFFFFFF508FF00FEF6FEEDF7FC02F3150106F60803FCFD030FF9FF0CFD02EF0510F6020801F70AF7F8080502FFFCFC04F7FB090BF1FBFE0AFEEA0407F8F4FAFFFD0FF5EFFBFAF7EFF7FF000E00F906FBFD01F9F91A00EF0C0502FF050BFAFB09FE04FA0703FCFCEE0507010707EBF3130103FD04EFFE0DFB0503020C0D0AF7FB010000FDF409FDFFFDFB010BFBF404F0FC00F8F007030FF8F5FAFAFB00F700071205FEFF02FCFCFFF2FE01F00C020BFB04FB020206FA04021008FA00F609FD0C06FAF009F60FFDFA07FC090E0C090CFDF9F7FAFA07FB04F20BFFFC0500F8050DFF08FB09F30104FDFCFE02FE0AFC0CF9F70903FA06F6FBFF01F305F8000807F1F9040E07010A03010301010D11F502FDFFFEF2F9F904FE02FEFF09F4FF05E9110309F3F504F3F807F2FCFAFFFA0BF501FD040107FDF816FDFCFF040404F9F7060AEBFEFD07F50000050B00FFFDFD0108FFF806F5F60D09F909F610FB05FD09FF0C06080EFFFDFEFAF7F9F70B05FA01F4FE03FFFEFD00FFFCF8030D07040B00FE03F3EF03FE00F5F8FF00FAF9F80CF9FC0102FFFEFE0B0108FAF7F8FFF805FBFE02FF06FD0311F8FF0CFBFEFE060CFE00060200FDFFFA0C00F30EFC060608EFFEFDF2FA0405030707000108FE05FC050403FEF800060408F301EE03100CFFF700F9F2FEFC040800F8F8070104FA07FCFE01EDFE020A09F00EFA0C04F70CFFF002070BF908F50201FDF8F80AF90CF205020BF41103100205FB0104FDF50502FB02F7F907EDF3FBFC0E0E09FF0C0505050E020700FE00090609F60C03040FFCF8FB090AEDFEF4070AF3050D02EDFF05FC0503040A0AED0803FF09F50201150F0CFFFE0604FAFF0EF801F90CFCFBF9FD04FFFC06FF06F6EF01F811F807FFF9060407FEFF00FF060B030100FF02F902FD08F905FCF50700FC0D09FA0302F901040AEE0602F401FDF6021703F50406F803FEEAF10AFB01070916F0FD11000E060204F707F60802040302F60F0B09F802030001040114FC09FCFC0A0B05F5FEF80F04FDF8FFFD01F8F3F6FBFB00070704FF0FF70A06F3050D06F3FEFB040001F103100F06FA03FD04F4FD02FE05FA06FBF40FFC10F11407F8FB0AF6FEFB0303FBF7FCF4FFFFFFFD00FD05050900F20600FC0601F40D070403FFFA0505FB06F806FF01F4FC030910F70A03F20809FEF0FF04FFEC06FA090404FE01EB010106FFFBFDF9FAF4F91703F7FCFC07FD0DFCFF07000AFD07FBFB050CFA0702FCF60E04F9010603FB0501070AF806F6000BF6F4F9100BF4F90904FA0EFE0203FEFE09FAF3FD03F908FCFFFB05FC06FA0DFC0001FEF803F60200FEF80D10F80E0B030503FEF103140A02FB12FD0301FFFAF8F905000408FD070C03FEECFC01FFFA000F0C050203F9090404FA0710FE0DFE0FF4F8080808EEF404030909FE07F805F800F7FFF9F4EFFA0307FE06070005FA0FF5F3050A0400F8FCFDF001F2F312FB0D030E000C00FDF4050904010602F705F20207F708FA050E0B0206EF04F8FC000410F0F30DF7FFF508F7EAFFF9FDFFFA0504FFFE09F5FEFE1607FCFF0AFEF40206FC03F401FBF7FAF7F50402F3F8F9FCF8000903FEFAF801090504060307FEFE070E0AF6F501030FF501F40407031007F90206FF14F7F9F300FCF008FC0C060AFE04F905FEFC080704F6FDFE01F8FBF0FE080A0509FC05F803FF0E02FE0E05FA02FF1000FE050814FFFB03FBFBFF060CECFD01F901060705FC030B0BFE08F5F70300FDEE08F1F5F70204FDFAF4F6FF0705F50AF5030610F90F0C040DFE050001FBF9FCF60A05FE05060107FE0304F11104E3FCFD03FCFAF7FF0805FF0FFDF8FFFF050101FEFCFAF30C08F2F30107FBFEFF10F0080403F40309FF030B00FB04F50DFC09ECFBF506F711FDFEF401FF06ECFA0600020108F70CFC000DF9FBFD0111FA0002FA01F90008FF0B09000AEEFDEF01F506FFFEF909F80CF806FF0503100206F0F700F602F901F202F9FA1402F705F8FEF80A07FF0CFF000DF808FA0FF7110701FDFE0CFE100500FCFE03F1080EF60B080603FA06FB0802F9F30D03FB03FB020903FD080700FAF8FAF8F9F2FC0707FAFF0208F4FFF7FC140001FD090108F80D0AF1EC0AF909FDEE0500F1060507FAFDF9F6F8EE0204F3ED0308FE0B0101FE0C02F303F5FD0BF9F5F00C040102FAFF05FE0C010408FC06FD03F5FEFA030700F9090109FF0A000108FA0901F5FF00EDFEFD070705FB0600FEF2000E09FDFDF1020500EE0503F2F8FFFEFAF8F803050B0602FDFCFA0C00F708F40602EFED04FF0B04FB0115FEFEF6FF0AF7FB190006EF01FA0BFF02FEFE05FB010107FE02F7FD00F10EF4FEFE01010302FEF4030700030BFDF5F903F503F10D0904000A0A09000106FD0DFF02FA03F701FA02FD0C080F0D04FDF603F6F7F900F8FD0609EE0BF702EEF5F4F4F70105FBFA14F708EEFDF1FBF901F7FDFCF3F40204F9030401FA0203F103F90EF4110704FD00FBF001F60905FA04FD11F305FB0A0C0406F7FB04F6030A03020AFB02FD02FEFF0DFDFB04FDF9FC060B0905FBFBFB0BFF01EDF50200FBFD110D070803FDED0D010C0B0006EE0302040108FFFFFE0304F6070102EA0EF90701FD0711FCFDFBFF08FB06FCFD09EFFB0DFDFF01FFFA04FEFB0DF90A060D0407F8060D02FEFF05F90AF60DFEFEFC0F08FA0102FC01F6F6020501F407F90DFBF500030800FDFC02FC020AFE08F909F0F8FEFFFB03FD10F5F3FD04FEF80BF4F5FFF6FCF5FE03F8030107F500FDFF0DF2F2010AF90007010B07F2FFFB05FA080EFB07E9080614F6FE0E020206FA01F4F5FEF4FFFB03F6F2FD0000F60007090103EB0211F1040203F5F90901050D0F05E5F307F909FAF8FEFD00FEFEF9030C0BF2070605FF0806FC0011040309FF02FDFCFF01F404F80AF9F807FB00F5FF04FC0DF3F504F90DFFF900FD05FA0908F90303F50AFE0CFC0DFB0FFD120906FAFA0813FB02F7030107F700FC05FDFBFBFB020D030BFAFCFE1000F9FEFE050303FFFB10FB0104040B0B09FE0102FCF7FCFC02F6F9FFF90BFFF90F01FFFEFEF9F6F2EFFC0309FBFDF701FA12F605F7EE06FCFE04FFFB03EF0814FA05EFFCFBF0FF0BFB18FEF7090BF3F50011020C060604FDF015F706F30EF30206FE0A0403010507F5FB00F902FC0C03F901F718FD02000703FAFCF70B00FCF3040602FDF8090106F800FBFAFF0BF9F902FB0615F9FF00F401F9030709FF0305FCFB08FFFFF505FB00FBFA00F611F9FC0A0A04FE11FF021207F7FBF702FA010305FE01ED07FD030E11F900FE000101FBF608FD080F0D0DFC0BFDF900080F020FFCFBFC030513FDFFFC0EF7FF07FE0C09F4140602F70201030501FB07F30C040D0603FFFE010603F2F90CF70203FBFCFFF2FE0801F507F8FB0405F8FD07000C0102F4010009FDF5FFF2FE08FE01FF07070908F7FD0E0601120205FB010606F8EEF9FDFBFD04F80304150AFFF9FC09FA0BFFFB03F80B07FDFE02080DFC020CFA01FDFA0B0702FDFBFAFF05EB04FC03F1F90C03F9EE0F030B1202FA12060505FFFEFDFBFC01FE02FEFE1400070AF8FD0204F7F80DF10714020805FE02040106050C0315F60704FC1201080A0307FE0004100A05F407FDF60AFEFFFCF40400F9FFFCF9F810F00203FBFFFF07030A00010E04F5F106FDFA0EF80D12FAFAF20EFB05F20307FEFFF2F809FE05070800020009F0F6FEF7EC0A0601F2FEFA0AF508FE060004F606FBFBFC05FDFBFE02F9FCF7011100F6FAECFAF7EF030EF9FD0DFC000202FA0B0103FAFD03FCFAF8000F0703FBFDF4FD08FDED09F70B07FE0006F5F508F202FDFD0BF2020807FB0FFAFFFD04010408FA0803FFFDF7F00001FEF4FD01FDFB0E05F4F8F9F6F3F40406F709FA0EF805F7FE010B0309FBFBFE0706FE0B03000D0701FCF30610F3FEFAFFF6F80311FF09F60804EFFD0F080706F805F104FFFF0605FCFD05F9FB040BF614F5FC0502FA0705F4F6FCF407F4FAFE03F6130600FF06FE05FD0208FEF90AF7FCFFFEFDFC0EF6011101DC12FDF70407F609080D0CFD02030AF4000305FAFAFBF10C05F6F0F9FCFE0009F7000100FC08010DFDF805FDFDF5F7F7EC11011103F9060505F4FE07FE03FC0308040E04FEFB0305F9F5040AEFF9F50601FF010AFE0C00FEF6F9FE0BF80303FE0303FEF40A070D080308FE05FFFCFC11FFF501F9FAFAFBFAF9F701F9F5070703FAF7FCFFFA0C02FE0D07FEF20FFDF8F80705FAFF02FE0308FEFC01010C00F70813061103FFFC01F90CF3050C010F05F50101FB1202F805080D0701F7F302ED0AF40B0CFCFD0005F707F813F9FE02FC0101100401F90CF70B08FE0108010000170004FD06FEFCFB010106F9FFF80702150D02F902F8F1FA10FE03F9FB09F1F6FEFF0906FEEB10FB06FE04FBF5FBF806CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E4F5C254B6292FB5C3DB9561B8793D8AE3E1611423AC0A9F8CFC13E1C85FEC6B5");
-        int smlen = 2881;
-        byte[] sm = Hex.decode("498A996E2B6C14E9C84617F4D63149C4676855D15CDC54A85F1BA00CD5FE550C6120227A77605DE227878113DF3137E13ACEC54F42856C0AECCEB53E1E96760ED6DE0E3F353ED4E4BCDB6B1AAA5CAD3CE1DD882C6BB12D3CF9D1C9888624A54D3E024783DD2C39DD54EA4FFEB280B0E546008CA0C779C8917E8FBBC7A79BEEA32D5E6652FE93D8B4737D47D8DD0C1600D5DB9135B74C0ED6E94D9FF02D06F951FB680902009EB68C34028EEE5176E1D64EF869E14C0AB577197444149CC6EFC60871899B1AC9DA86F8373A1273A97BCE318F40BC6A2AF35CEB52A5EA853BA0588531B87B0E1170EA6F464948A55A84920C97AA9D3960B9A099E947608A1E393C634DBE5DCBC6F0BD55E853470F6AB286CDD126B52A29BE7465542D477E3386821901595597E4D6583B6E16326D4AE71A2967FE20BB755A12CB7037614D3600A2B02D4C8DBEE17670CE7B4B144C3D2BEEE9FF6049B94DE4011DC401687E83211AA37E7A9D043A0582A9A7DFDB0B5A3A4AC8CC58F9634DAD9730311F2CDDEE205FDFBF160DA631FB11D0D0DA9FB42223194B18A5850E80C3537F8148A965EEF7B9A2A81C69DDA4797B28A0FBF4FDB31559F940AB7F47021CCD5881934487A6CB751845BBABCF3D213BAB2C61D21F06927DFAD28581E2F8377E2A36319240DD8380BD85DD1A5BF4828AA4F8F5E11534F4ADFCD1B881DD8A094890B20C4F67F5C1545B4065B84F0B28A64630E332ED2426CE04754BD23E52A90299FF399B240D04CD6677E782409D7AE46F5CF718BA1227CE673C42B1CC0E759429F6486AEEF5CF9BE96087ADAC32606B3D2FA53254C62B9617FE0C8D0490C7C7A77C9D54DFF076D44DE2864E3AF3037511C05D2603354C2AC19D94B6CCB6C6362D57B85C6D3E40E5960F7D3DD00ACE24B07AC41014CDF58B106A6D61B0909DDABE19AB78FA29C5987E7CDE6D5D084C278417645995976C74CB7D9A7D6597A83CE1654209325B788F056220F672CCB16051EC94B34F89B6DD33859F6961432294DAA66805488E894BBCE31BD4BF29BBEFA3B987857A4EA2F87AE9F6B5EECB8BC3DF90547833F3240071AD5A5464CBC9F42DC0E9E96DBBFE1AB2681CE2DC100F07CFF7480DE919AB27A2F51204BBF779380B337A550371960757D3BEFEE45AC13792F9F885AC22AAB4A5263364078AD1474B206BC54795B69380B0056F9CE8325B7B780B6AF7DB44F3AFEC558CD93FA268A6AB9AB6714ADD72C93B5BB011D974F8D2D778BC476C5F41ECF7C7D48E632716EA393973BD67F7496078F937AA45FB33E8ADA4CE5192DFB244A1D8290BBD29F239CBCADAC9E1D260D22781B7B41E8A42ABE21FD0D3E3517AB941AD09013EDE52EB9081F731A7935B14E1A9132FF2EEB2C53ADDBCDD3A9656F5E22B0F6470DC961BC0C1B38BACE2F16649536D28271D7BB9C76BE5B63ED2663C5842335A783C524FCEE69D6964874D4BB56418EDB945EFB097001506561A1ACFCD03B15B42A009C9B150CD1FF57104737E7978185F980B333523FCD89F522A59055515A4895E68C07161C47320CF1E542F03EEA4CEBC76266E2C85916638CA4C054F0F085BDCF2A84B5666932A23FF8A40EA90F6CE2888731E88FB7D90CC24D66934B2FF378E662CC57D516072698CAF09EE3759AD305A30C0C222992D3A22DA664A25B74043D338DCFC6A70C786853AABF31B6B5C49D2BE0BFB0CF214467B511643106E729F4756B147467530F9477503BFB9393D36D4A9F964956DCC0C6ADA0A4CDA17D794F7C6A32C7D8E146C6769B810F97F9FF47BC187C57158684280AAED7807D2FF6B9AC3F4E11CEB29F112C30903983FBF38F5D118F084A5ED22E020F066BD458716B491DB9A9A26AFA5C187FEC28BB67C878F82EFAC22675C0D2B6D62DC1804D7933D68441A9F66419314D66540F30BC7B1A8D1F10F5677BC3C6E236F6D5827F70E3F471686F640A8253582F0E5FF146C3BCA53CAEF22BE026BE891BDB96697549DB993B9A4A1FB4B8034FA9EBE42F7A953155A050CE106EAA94C885E225F2FE61E254BECD43E34DE41385DEBB456CFB70FF8C0E46E19224D5FDB7A38F4D2BFA314DA933D8A93CC8ADB99344641DED33E8AAFE639FB38AA9026DBFA1F0C754192A57B8933C99181DF62C9F8101B86A743BF7D07924EBD6C14EF54CC8ED397D73AE2EAE7E2FB4D6456EE7C68FE6DC0912DF23414FC15575E7B84C161A5BF8CE69738211BA13614B1A40E7507701DF29D3B50C26C547C8E49537D10AACE95424520340A57F1B5B35CE62DC693694A2A68A95409D52DD0A0F167F4212826811993D4F245E56CC36A87C404C3218388947BA63814D3CD5DB8666EC2E2551E8E130E1A1C998F301005A645BFE2EF1D208412F3937246A5A41AA4F3D3A5FA2833B4A30637EA569EE3B2E6B72D90BCB4D79A7022824BD22FCE98905D2DBED895C069031D08B69E0B710B4472BE7BE2DB52898BFFAD6F65D73FD28C8BE578A152E53C9D2F04E206F9BD0714CDFC5FE8E17AE4CDFF12B9CB2EA5CD8CE7FE81B0033C9E1E91A88D3F475012BF6882F3D5A25C1E5BC92B89229D38CAF7314DCBA05636232E8409CEE11E7EA607FEA2FB2E2FDF07EA3994A3129DA426D64DE74186A1F8B432E106B786BBD302FE40889DF80A5B1CCFD129EE0B0DBA5C1D506A8CC69106D6BD5006D2BAF26D51DC9BE072E4BF5EE4D1DF9D7B4235CBC7DEA9813244122DFA753479CB66128753E5C4352D0CAACFC8CC577E2D689028025D72EB6A6BBEFD22F2CB01D5013D86A5F92A1AD6AF48F5B7C9F6AA8BDABC760CA25C0ED93986183F00CF3B9F37385DE183EB614D267DFD6E48C7AE1CFFBF6F977512C756103FBC5AA332F8647CE46D12CCD597A0D982C0CB30D7EC954515793CDB3CEB143DFEE2552ED21EA089A486CA6137CBE87DA4F27008B3758668DE776C46794303E316A3480ECDDC2CD5A97421EF9369CAF27E6AC5A56B7C9D42C64096EE13A1F8795042F24B92566021CAB8A988EA5E5BE324A19CF96BCBE0E5B1E32E932C7F45470D99D785BBEC8F9DA12EF268B330D43238637A16D699EC842CEA24E9BA3F2C58D642C9DC7C482FA73644B5E48B127573237D1F1821206081B7C34809F9839569EE2D2C650347B0EB48B76F1387CB79AC0C6306B9206A65E9C7B1DA55C153FC9D509F5B56AF7DFF6D3909F94E1F7FB57BBAF68555D2203B33F459B5C449113B559EB0272EA9C0D73469B72256C3D730E2752CF64D1AB0B47196FD8C07EC32D2E337242874397A0D5A37ECD4BFBC88C96B1655460FDD63B265D822A13DD20B8837F727BC1ACD58AC33603C73A214E90A59133A72B126240FDBB4BC450FCC19ED6E9FB9DBDA4CA3860A2EAB2CEE7D8A4534D2786695D6DF754F5EF52ADBF642C5F3A46C9A3CCF949A6E6B025856C07F801DEB544BD70C46FE86D4824246E56F171A81CDFD44730D55F89821A0BBFA449B9D0FBA039731AE81CF0612CD4316B0BAA743CB7E9DB42708D40C60A95973DC9CED8C5FEBCA232B093D7A5D6342325233187B9B2593288F6ECC0F163315598AE526861DEB3FE0A1DB1BD649D9550BFFFF90AE242DA3E2D67DB09C248C74C60EA83BBD56490E539C5D486EDD707CCF6C310C3E3A713A09183AD749D6FDCA2959AAF0B9FE73C2FF4373EEF3D4085951EB92ED015733D35DA818F48946648140A01951FA411FDE51792036B04A4A4CD3E5166DCAF152346FEEEE3F585F24532582CFE064DBADC33FF102A39BB4BC05247A3C78820B2639CE455866A344B557ED08172184BE286B0F8611236B68A9A97969795A6DF73F55D9D740B21C7D4832972EB79E033BB48F39B4560B0CA338D85577F76F38BD69626CFCF6969D40102291DA41BD67E7BCECA8BEE6D3AD58681630DDF7EEAE5A50F19AA0D1C353F36377D0628E0EB299F0464063AB8B1CB89CF512001949E1E66C68A750871AE0A3A5FA2108AB4E2920A08892D68719E092B48C3043325E5944F62D4098452B4A8CC63CC0792E256448F42C8DF5AB191A13DD00033600D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        byte[] pk = Hex.decode(
+            "7561DAADC32E5C51E5C7AB83FB32FAA58E32A0D1E5134A07BBBC3AE889C85470D024D22EE8A269A9772C24B5971CEC2AD55A846C6D8DD917B76223B0799227F89FE8EB91020CA49BD528FF1915ED00D9F9709740A13110864F11C24CF0DD8DEA29BF288DCFE4B1CEB12A7135C5142D7F51376456892BEDF33A295D6D70AD058C51D565E8097D26440D57DDA86AD04C1E7ED45946654ADFE9F46A2D2CA0BEA6B2E57046D734A3989BD6B2C42B9413646C1A2F1B6C3E55502D8780681D1553601130BF98D26EA05C216E7C98FF0D5FC03D10384DC6E29316921F50AD19FF96772F998DA91AFC45CB3A00E67BAA713E2825C6DC2A0C0E8778444870A0483E588EDEC82199DA34B636966A85D9001C446C425415C8F8269697EAD5D69DBA92F77228808FE2AC5485612B0F28862D9EDA5D32D5CDD840BD45649C1D7C50044CC017D87E8D1012FE2EA0E26F2710467A13230C73F485F26154825038BEE477CE3154C36468B22124038C3C0A8E41C8681B20BFA3493CF88299E70D0D3FBCB5CF7EB82D39656A27FDA96E62CC84FE9A83DA858A9982F01803872833C48376E04C4E319744EA26EFD8FD4A1834B5058986DEF96CD22F4A30652A543D640C424AD107B54EC7D5E9244328BCBD166100969A54B003DB5F1D30B7D8462941A5C2E6E166563C1D7C7222D167E2627C98EB0F4274FB098C1BB288C8A12180E0A2F32596B54E3E68520B625B87B47F5E2FF64A1490CEC86E82E5E94359FDF6782E2138229D2C1EFF49B6BB6A722D033FF80BBA7A2CBC90D40E39E2E6777E062F4603BF87C407E2C20DD67E67DA5FB1AF696A05CB301B78A91A54F0881D5459E35E4070E0FA605C43E98C472C76DF9C31AAC8A7473DE2B989F297B230B751B4F32B61CF089F77317285D302011D7D6BABCF4C07C0C70AD4DE3E99C46EF595EA9A61A080DD48C74A0C961513C845DB6A5811750145D081B4D4E76DE5B15DBA5C1626289345049D5CB8771C7C129DA1C5DD8BEE596321647E853BCF8D661D194A1023845F420066244F6BB5A0D50533579AA0DB5A726DAB32EA2B35261AE5B6D0DEB12C31A79242E2D952AA1A1774CB90AE815BE9613A93B30AF30374F5C8181F54B4F5D56E07232F68C6D52B94BA64ED1B2B7CA37EE6C730E03816689853224242F0297A017DCE955C8B8D1B41BB3B12665759584D3373BC2C77999205ED56F6E8B6AAEC3346D1528C2AE938CE21C24400ECBC4BB520958E3303374C68A21D403A6E6F05DAB6A8C03FF07FF5B83A216B2EBBF8E357BCE51A52A56EAAF6B6F6D34B8BCE02E22BB67CAC6629A8E8C3F9F4E7A3AEE3B9AED01B2E3BCB3FE566E3DFFD8C4A9830AEF3D30DE63B5208A32433A4C42633C4840EC5493E891D22F409E35F02CFAE46A9D3DEED6C221916F9B27BD7EE3C1CC3C961E59B3DB42A59213FFB168DD170393832ACA8C79B52C194571681F0E8AAE2DB99C1201632659AE136E3B085985DC94981A5503D950E041763AD485E253DD8B329283929E2132EFA6B9EB08E79AF6027D6E2E7E30E3D6B785256BA2E012D003AB5C07FC4A0EAB484991B3CBC3B03346409491A876C82548E2BB253E344E80415E4625D3BF15975B59DC36FA00A7FD894D363172DB8C2ABE40364F549A00C5D5FA8B534E0CAE6A9AE83CB5E4BE6BAEE126B755C9CB8F76C1C8A6E6FE2567C1CBDA68370D4D8465D8B360880601206CAC0C9D0B67C0261A0E7D132CE9C4EBE21250F7F197480FD5E0D8DA83068EFA8B35475733FAB080BB888E1D1D89D124AEFD81BAB3D970FE059674B6E71455517B9BA322A236D272144963DA4A934A1D59D19F05FEECDF0F1E390376801E0454971E30C47109944BB4DF414E19326B8BA2013D5F6B3FA20188E5E9BCC63ED8BC55D8D7D3A086193F9E80DD32381B26BD534D34F298DC06378EDBA2E614E8C3D886297AD1B2B59FF78B8FEC0760336C5207082D82ADA01CAE49AE6500AD992B7FC0631491B909F449618E5F82B18995BC926F46BBEEE152AB2706521AEE3574295F26FC01A7AD154673B2D3B500E1043FF51340BAAA70CF8B3A65C65811318AC7B4A32A9519A00ACE331959AED26F86CACCD7A952F9D40E66FC31224AA1BD280F89A6B16F46AC39C5F6D709868F92D0FA519D39827D215D8BA7F2FA6B4DA67D5A791984E7A1BDB04A5A7B024762952121C109AA318FD2CE88EFAD90FFCC17419C2892C916BA7E7C482ACF41E5C386290F1174AE5C5BECC5A0D21E23B40FEAF220CB9A58AF8A5B5910DBE823359150388A4B0046090287B760B3978433317F4C2E1AF2A5BC87EFD2F59931F558ED226CEA7174B9BD4D1BBF6A10CE2C2D827DC4D36B2096B9309D42EA8D45B5AC2B1F7049CCA4078D4751B6B03DB06B8BEF3E119589CA61D7FE308AEF4561C4EE87DEAC2DB94C38C4760916D27E6BB2C169D1C88D35616F8C5EA6EDD4D48D6A5CFF409DD67D5BF704B832C3454A8BD14568B93F760EC179ECDED970C46BE39D5960352D2F5B3959758CA7A10C88E961D474449CCC21394D2099F6110BEE145E7005DA5501C0DFE3DF1094ACF99951A2974808A60108643E9F4177827A3F616009FA67E21F54C363BF8F162F33254D6CFED5E7C0D4AFCF9854620B9632FBE147284C6739F5FB4F211C20B1D9982215C2C4B1C02CFD041F8281E429940E8FCB66C32B694C2611D8D7F50A751D3969C2A3AB1FB2553CF0B26C0478C2988D9008EF4A1777E9F7F16122BDF208A5044BE728E9D928A52F133C1CC462984590D44E00FF7AE1254928A006054D008E780E5D10BA7E11E17E296D43D21F64544AAAF27E6AF9D4C7A381E4CC2873F358B3541454BA2D594602DA71273F50F99201CE60F93E1431D82D5402E8654206DFC651A3E36CA854A9B18E8DF0CE2809FA89BB465C68475020C51A773107D9B367E3C9A82370773A498FABAE4AE84D7685C9329FB226E0F5F6173C5107AF143BA84BB2CD9A9D2CEF930C5F8055A4E71C1CED7538F3D8FD194EDA2C98FFF3005BE8A73D09C7CBC8DDA07F4066BDCBFF23A1838520AEE0A28E69DE30019DA6AF61FE52A6FF10B4A9215F8F9ABBC9FB5081CA33463F891A53DC91E252E90EF4B0D72A35923208CA48C3793347F3D5FD28DE56538586D2C2C6C1FD50C4160D67DF11254BB36CB94B835734F5B831BF12845155B49FBE3B0F44CC5D6782983D9478C6B3A9F30626A797A2CDF8911F5BFA90E611B39B29314C9E4CB7F12A69D6DAA1916667AAC5AEA693B70BD741928014ACCD125137488BEE48BAABB02DA3C6A9604A16BF4D19F1C0D87FAC94840C721EDDF03CD8A841D2EFA5D52BAFB50726B45A476C47E251601B0127C092A4EB8A66A0CB7A2DB5503394AD36D54F37CC6EC73317E38AF9FB62BB758973971EE9E75E6D8AC67A0D44384439E7B11390E983975281A194F8173119905FE8FFD863D55CE7A6AD00683D4FF83263C81A447948DBF3F1EA2CA593B38E6719A8EECF654774599AB25D1EC9741AD3DE4DC23264C9700CDA27C463656E3A53C52B860B3264CD9FC0334D915E03EE74E54D4A2E2D795297FC748075A404A6C0AA6A22671A480AA3468B9FA9DA9A20DFDD8BB9100C8B6804DD6F16AB0C1B938FBEDC2F1A9DF43CC877B0F323042E9B1DFBB8A2BDA8986534D52B7EF06614DA22F63951716C2B04196567698552F3BA2067B2309AF06DB126727E1A82F731B0CC2904470429C1EC94456D1E66A0EA35A2A1C552CD3FA1A99AD3D748D563978800C32F5B6CA195938BA26CF721FFCDD9402EC4E37A9E1198D39516B4892919327592C67ED148672377A53C7AA9BD47D16282D977A40BB7E036A3FD11283DC2BD82EBA452474B827434014D0BF7C5483F28A432D378C6C78E6470F4026C27F531C367A2B950B0DD95114A106C72227CC33779C705B1C8926DFA2F996000535A0BB87BCFF4B9A12D829D04434AA579A11F6D15490A2C9FAF65042DB4CF88BE47368C589629703815FA6A2A1BD4A1447330E00CAE7645B2527FB82E864290C9574FCFD364F1A4FE3419642D24D98A6A33A0FC0E83505FD5151518555F9E03B2D5742630A119B2B32657AD6B2EE8B624508780F98B27E51BEFACBF3A7B920A282EA45358FA4145AA24E7CDCB3432C6F08D92421E2561EF811F1C60D7E51C840EFFD637D940F43D655FD1221F2A41027A71D90F978CB09E2264737053330E47E4C433F9B5AC94DB9B04EF7326410CC60DF7A220E1749A654FF6370D7C8197C808D35C2B6769E7CCD009BCA5835E4FDDA202D6C3DBE324AB97C879DA16184123CEEC3D5FDCC3CC71103E46E711CF8274D29B5CDF074BA889D6B0E51290428E0499022E634439804A488483D3927DDB78C248EBF9A6C9A678F4F482373580CB7FE2DAD993DAD34149F112D735C7E60DBA06BC44F0304551ABC09B9C91D4211FCD6025A865C90159CE3D842405A5143C46BE19DFDC64228A413D4225D7C94697C0F128B4336E9483192F3024014D2E9885447D0762F802087C7654786F76773DADA83CBC7396E5DDA29BEE95E93FD8C67D75C7D258765C7154DC822B925E8684DEB3443F85300C96E59192E8E99D2BCA21CB62713CC5EB23BF156FB3E97DAAA0695D84932FB9C9DB03E0BB13B103190246CD9AAF79C4B9C8916DB4B6BA02F5FA8B87B9E9B8D18DF6650E907E8A02BD67B4C00EBFE4E4C2CDA560D1874D7215CF0009DA57529EA831A8E71C88152849A8C2900134306C0C05A40BCE42A17CE9C025C100277D7F0CB35566BE5AC11B2466A2BB03B8DA8F10B7A30CA26FC4E0A3971E16DA0C5FCA52838361046549A77B9721027DF307AD3DC8B4EB3DC05BE4B0B0FB77482A794305BBE99499F6ADB05519D1D4A12740F91A363602232535458E94C7487DB90E777247864446B5A862164D9761DAAFEAE213440958E0C399B4DCC6C6BCCA44BF33512828B20402EB814384B9ECEC2B0C1316F3C545D4A65F31CC01B9E324F4468ED92896AD4572248E7298520FAAB161203A09D366EA8FB0041E8A79368822C557C67889DA58C63043C0B6E94B36C61CA41E5BB570448894EEE09F4F00A18A0164FE451B4D005AD5B9A1E3013C356652479D340594223238E068D9DC8C838902D72D97D35EA16E9B7A1852EF464E22D6CB4217BED00F1998134CC09098453C34CB9C0EA8ADD432E295A4B8DE30BDE1AB413F515153A75D02CE99B2EC8E45B3FBC1682973B769D688C8F6FA1AEBEE1795D1B9563AD1D462BAF1668D0426C457382E0EDA9A898FE694D319CCFFB53F378EBDF3AC7BBCDA8CB319470FDA31A1E4E920237A31A7F31392F54BD8991A0A98C65E8DCD9A9666B4403CF1C47B76D30391610161253A9057D23712ED9C71816D1C4B32A390AB6B4DE8F6D6032B8967B30471E80E2FF925DCA64887EA29FD14051BE7856B65B954A37F9577384245272B2098990F198EE1A9ACF1548299512F541E45922238F31115532A3AD6D744473F68DDCE411BEBFD933BE64AD1C7007258DEC9B112D4B9B5ED01FB4DE74E128C90BBAF64763A540E41B407151CFD8C2048A415304F05F7733238C6722AAB433ECE0CF900EEB02C24B20DEB96441E947ED1097A539C6A1B9E8B838E6CA2055ECC7064F43495815DCA3972B83401D5C8EC61708E692130512573672CCB5B9A56973E2EDDB1E3E1C965CBAD617D2590C277B91985007493DC9909BD6BF8578F2127141993ABF817B6DA2019DC6CF79207F96AB032B6D06C0512D049EA8663C46D5EDEB246F036C978496B1C1BC47E81CB63364F47B6913D0548EC13E1A37BCE62C68CE98BA1C29E71A3868491569B3093DAE40D42A5ED2972182CEBCE8F2589137699B142E3D3D7164939A081FDF237AA58ED794603943F2269A40B2967314C22E8668B15FCE244E145A3A49A3CE5BFC2C46ED93F40816A92DFB2880B5C4E755B2CC50914583D0FFAA28FD934E763118DA70C3D4137957333927FA4F0F637C4C52B9078E141089BCEDC7B4288019EDF92FD0ADED643C375E71C25ADBC8F94E04CA5A4E91C362D1C86BCAD31149636A895F443A491E6F558F9DC89ABA27967C7E66EF9C7302C100E4AD175021CFC295048BD5CADD8E86469EFA437034D7A01E38BDC64A6187F51F6D023CDB240F9FD0053FF7258D2C5802D1FA1C39DBC9D6D0E65026FCFEEC8205F77FBA1F64AA03B4D1C091AF43AB800839B4EBD378B28EA895111560183199390C3C6706EC35A05E4118A192E5A29C77CED86919C2A74A827913219B2C33E83249175B886B598CD25253A4B07D10C3AF92060C52FDA89B880923595865CA108EB0E78BA3F9E7B0465522CBA8DD6094131045E13A4C979B07BE8BD782AED050C3CF7C25ADE3C2A1CAE81738C55DC1ED0A33020EB6DC3464BDF6C16CC8223161991C62D149FCEA52140198B36B6379B47F5AA5EC066A5D5F561FDAC44EF3F967EE0E0A420AF89E22A053119DEF0D36FEAD69FF1B2C2476561DAF68D7682BD807CB4D2782C0B7FC39D7C6494BBE622352B534C21BFA25E6A37613CA73B85C98894EE1E588F20E73643F241F83003ECCD05B56255AA36782EB07283337E99144CD03B90680A048D4538D580728C0AD59C082104CD79099A08C193D40C3CAAFE3DBC968F9F8D9D0C74A7CA135F02C8F9C18380B00ECA21AE7E7A22133C1A65687C7F74CC5FB4BBD291183B803B1AF8441900134932217783D9EE86A565C8E9D316E0BC93E75941989C7E227D3170B0F78AF6F1D8FA70C697B4645A5F896B0C4971B6544D0C9786A774179E99EB4425C9838D57861C88E1DAE2E482D38BAE9D9C04DCC71D97449B13D3E9F6A52D81C91C0B02B581F4392C4EEE0514D4D39C84F323D7FE9A30FA87C5380913D3955C210B84444FE7CEDECF821D903E2102E8CAEF4020006F67B6D4B2146335AF10C6EA0A116A0110C5B9C935E349A6B29E8D609361B668E42BC0CD1E33D491D59FFD4A13E5B7742A8DBC918FE7F72BF18268121CE414D222C9AAA0522CE0487F2FB8545D19D7911BDCD4C81106EBA729FBD57365B1DE562A07660EDC92337C9A695F4DE495FFF27D63248BD4E4A96E405E39999A8AE35B6CF12AAB699FF741E298A3222D168EEE5ED1C0A372810A883BAF50E7DECA6F146F6DCFF1067EF4187AF79B70D8814CD50E39042417B4A1B7A230FE68504352093E91073307AD525F069F15DA88856C5140F8E89DFC8861E8678067FD054B163E02203E06AE92FA76FE2B68527737D99F2A13893699D7985F2F025DC3A430555FC23C9CE0C078E83CBCE363C5B5840FE09155F433178C46D257F5DBCEFC6AE9140F46A78872803B8FC6D0459C8A26279E1AC09711150E7F58F420A6AC2111647724DAED3867FAFAA58D4485F0265F0FF6A63C9F88CAB0FFBBB3579DC045903D682070C4DF15D3D02650213EA7A7D2FEE676BA92F923C1A0CBB2DADE5EA8F5623A548DC1060A2BFDE25AA6A09F80D8613D9673B66D2EBB124452921200BE54364DC705DC60BA75F2D0540C4168E6CDA16DA504D63068DE764E1823700BA4C97BF86058DDD2F5942DD5F4D3B19D6883412B6089FC6A6DAE5FAD95C3175AD53CBA016D59FED8107D0C64A1B50B1BC1380261A55D04998B99202078EAC318A36879D5C5AAD9299134EE66684179F8CACC52EE8106D23D0509D32E0459384B86CAE143B3E3C0EE95C8ACAE8D496E59131C80CDCA98B812E0452B8B9EFE94DE5424C50B935BCC927FF395D4D141DFC89A639703632E98451182D4D072A987A88B4409E0B5AD6F0CC62A5A10C3DC00B03597D1453B3257770E36949218F22D721302D3CE5DFC09D5604C943F73D75C996687931133DC4B6B061E5E74483FAD6EAA9E89D9467CE4C4FA061EAE71B02F3390DA9EE49D0EC92097321171D9771B1D65A4B54130345278FF8BD415FD5C9894B9F430E47F2967C650E376E614AF5F32A9456AB0C61A9278109404A484F28AC568922691C4C3965D465CD82FDFA330E3C9DD9E65687AD8827D18617C54EE0E3F6BF7E9FFABE7127AACD1B71573E2846AADBA44415407013EC01C5A7F70960870D1742DFD488B81A34DD7A286F4CC2C000E042352CE0120A637250B2A21AA998CFD1CA99BA21C321335F4A9222486AEF986FDA32C36F64172AFD225B5D358B59A06FBC1D388A99AD5780624E6A257A96910A4471C9ED1CDBF4AD41C74538A0933A460FFFA7B36C925E02D45D89A819725B8029742C189E561840F20EEB86252A6925116578A705FAC7B5B9A128E1B429E321C0B8EAC6B74C9590E6B02DEE15A0AEAB25B7D5533BB7B6D9299620BAA42C097304795B71A91DEC14A32B96438B0D4C313DD9A25A916554D2164D787E800B524D7E02215515244E7994C427574CA0E02FA18244A81E49C03C59E85524C8A76B77828A938A0BE90C77595567FD2619381FF0FD601777B7C226AEFF35E0B5BB7D6D88660027ED4E5E4265B2E0A775122A2E375F08DC24F4F327C9C2766F9CD8DF228AED7920250F1730A4A7D08531A26827EAD62E1BAA0A45EA9B8A26F5EB7CB6B36561F72FCFA31BD4509FAEC210D79E0E86B3048F8E629DABB24AF46C73547D40C0BFAB592078383832561F15DBD90E4073BE80079F3A489BCD51B98278337077F6791CAA0AC828A14865ADD78BBABF1113A171C06D10796415C7B0F4F3B732745BF9BEC82866D4508C1930B22E391FCB6D68AC89EE0D66A8B76BEAE525BC89AE9FC4C15A21169B7FE739DEAA80A7F19D5C669EBAC95A71EFE265A1570400315B8E853B7CB18E33E3ACCD4CF9E48546E36422E6E0ED282519DD5280FE5DB8B7877902188B17C4120444D729920963CA5AF34B94E365A8B0AF7947142C114C3F0A7FFF0714ACC3833CE4C0864185ECEFC7F36736F3E267DC956B0A1051ED765DF0BD12E1D25760A09EB05C16B327A30BAA1EA24552E15448BA1B3E1DF0E8550C7F4CB8240DD4146AB5FC002E33B4FAD65115439B10991417068B09AB2EE5B16D02C250A020B27580A9C1122604D1D69CA74CB478582614C56C4CEED92660B643C50712A132EF7D148BC27C4D1FA1E8680AE171CA8643EA4BE9EFD4F442B1BACB36221370D2DAAA509F78B76FA2611744D7617C12CE4B73914A26A0E64D15E8201C5BE8313FE0B0005102EDD25F95B440C96B90D9A92720AE271B542060B2EDB0245BCB3C83F901E2631B76C24768D28C18B01DA9C184DEF925D68C655E65A013B592D161841045F3EC54D0088449A64FFF0EA8E6AF632347BEAE431C5EF288A30AA74D95241EE7A6FF4E73DD22EAB04C6A6339CAA3A4E8C181E0751AEE8468771EECB0AAB3427493B0DCC2B19DD9A586DF437348C7CF9C5E3A8B1A630D5BBA6307CA395B4C797CEEBFAF5153258B325FAA9AAD673DE591DC8C0D589C61EB70CB7327B537AC1A31A6EA95C81C886CB3DE52427BBE10736E1BBDE9B8EA4AE6A2B44E7BC376B27C08E6FBB22A73B8D14CBB3BDA545B9430C30D6F3D67E0B42D7B122DCEC983B996188F79FE193F03CF1B446F166C5D8F2B1FBACD5C85678698EED8E5C76E2E0D55B955A2643FD1BBBC16D5F5BCA479AB5C1182FE2918BFD722DA84A010E60164C3ABF8137D293DC8719731F40ED404BCBF171E99F511F1E3CA723D59C34D78EFBBD463403F1E87337CA461447FE180EC53054A3F969218290042FF30D798B0C20A84C12A4D632CC9955FCE0D5DAB4AD68E71E5E4F5E446DA5FF1E2A5EF84BA2A3F222E4240355850B20CA5065330046E261E88156FC1A84442C60ECBF4C3D2242E27F8D806B022424D917CDA2830CC401C7AE9F1221FE533EABE3CD2B45E5DDC9379CEC4542B529977E18A8CCBD41B827CF900268CF7F6EB88C82415BA10930D1E157832F46E1B0F14E4944B720270C92509DC0DFC24F605A8AC015511B5554A4170F97064CFE6CF944D3B202A7C17DA356ECA45D92941C661B70B4AA679EC10DE7230C095F0590575FB28038E02A807ADD262BE38F3CA3F5A1D103EAF88EA59449AD040FEF17E702D2E46424A19216F2EA716B60070D5F8069215D580B296EDEA56FA3049972FA9A7CC947E1C56F669CD228B4C0446268BC47FB0D160EFDF7AD3218D3D57BC4B583319CA48A21D8D1BCCE5179421F776870F140CB9F18E2AA1DD2C5F877D91CBCCED0B4B8D84B111AA7160EE85B2C9B8AB69C1E3EE4431FFE4D3BD6A4DC6C8A800BD20CD8137AE1754BFC244CA7C5EC281381A237548A9D9AFFE7100B4254302EDD5461122D314B4938BA7B2A4423B40142E090E7AA3A6FD08578B40B17F189F9B16DABCB6DA64DA5470991398E9B3238DE4622FD2D12FCBA1928BC861ECC336BDE65A5BC43B6467CE293D37A1019EFBABA13ECE121B0ADE45AEB7C644C2D49C05B45F946DFB58E78B5373A4DB14884A241367672A563E3242D1856C8D1662D5F3691A6F5C769DCEF47778685BB448A8ECC5179EFE36CE6C7E5C04FB2644309018C064B2803CC1377D19F59D30B19E04D2981873CECC5C3E992C8889708B89E1990AFF746B340AF895962FADB2E2F926342137DAD8C1EBB09A616F658F40EBB28D3858D21B9F92A7FCE7FF2D421994CBE6072BCAC3E0DC3A030A212219D5DBDC906B07DBF29DB8EDCD213667F40CDE4AC7FB56E2A08A55EEAFDB4E71196FA642828D56983AAC268FFDD7E5BF02CEB2581B6DB158C91393A36C367F36AAAAFD2C40C2B4EB40963D1332155353412A2126C51BADEA6B842D2FE695B3CBA2E2584288304015905AFE6F808CF58A708290C4824CC484EED112384309A28E028D1E2EBD46CE419B5CEC1D9C9164C9A40218DCE50C9B466A8E3C357D3F86EE02A51F86FD361C000509AFB168275DDFE1F1BDCCBEB92CBB345E62CCD4F0328FB0A35FA8F28919927C1443D289AF808C8F034D28F79038DF670499E6521F080F0D365F2400D5D086CC7C0041D4DF80E4E4CE3906215D18EB720466CF8B43DE1C692BB71F63DF253EBD498A9020BA92517628263F65B8CC41A5D21BD76B6436029FED17639049766716B33F03E6F1786852AE47250BE2FDF8B6BC4F0084A823256B34A2C2FF4931119139FA60DFD0F376990DFCC08BA8A09B0EB3DC299CE091A1A15F1BF7C11FD35CC40BA9C2C624530441027ED45FF8C00EB0B2BDF7BCC18746DA7942C342D2A58A88EE549BD71915D1D907F0CF89C6E2CCF63025E0922E95E713AD78DDB8052E17007A83B966279E860A19F38FA484702A1229ED9BCE918B360B2C1EA70ABC182F62B2B6306802D70E4E2A83D46CAAAF950A1A20DBFBDA6ED1F21AF9C2AD2092875EB391A925951255D9FAE3292EC485B5C3CA16B003FEE565110E0A910A010C499AF322039140A83B28949AA8946FACC77ACC38CB4AE9E2AA250E34C0E63260BA989181C7470E0BD484C17704907A39EC96E2F4269CFFC95F311DBEE98360A999FFC28BEA9F9446A00758F83B5D492B602084DD47400864696BFB5119923EEC8D5CA82CF9F2BE623A7046A741A8F5A31259A37247F8EE9CF5EDA91B5BC5DE61AF067DAB0CE4C6C9881EFFB4E1D40C87184C38CEA0E07409AEBE92480294B852E21272AA71463C86354A37704B682825E1E7A1FB84A3329C376844286683055AAEC5C8BDD71BE9C88B3ABFA303E7885A0A027012B2B8EA8B83BFB1A8A8C4EC028DE82CC11CD18511E3F56137D84D326326AD2E0C479513F5F112E91454C6CA904010D328B94A26AFED5C8F14609037E17355E42B24222E7E5B1D84E61895C45090E769734E07AA03B00D08A0ECFE0967D7AE09D16843D54F6EEC12584F87EA7AD832B90DBBA8E39A0D21E867E3786D8FBAF19CDF7E56E9E9A44F421DD350D0D85AE095AE2F2BC0F9C7A8748279720BB0C0C6E7919CB83C40BDB3EAE1A6F87DFDAACCA342DF1C73AE02838ACCCAC609504A12F720356A57C4A3D21794A8106139C27503E6A6C47844962AC9FBBC0244601888B6F2CE6C41F8CB757F3E202F8B9A27D3057272745198A5CE7D2A4B227EA9CA6F0838B23EF2C66950F38693161AFBFD724EEA187F9D324C970B9F4BCD408161906A6648D18F4E6D52123F02694E39B6D8D1BE691C5AD20E793809F4D970893DAA133FB310146463EBF24617072871AEFC1CF9E885923416128479EDCCDB941FAFECE56906243A74FE19CAD9250141AE8B4CA6A343926D9076B964733D8978A109876475EC66691F659499CB552C28ADB0420E335F01DCC2BF041BD438E830FA09AE673C03930FF478300E5C1FCDFE6932AEBC289C3713A08B3136B37798527B2F67803C705107F985A1F4584AC2FEB24D195234DBFAF4097C1A1A6C50B6CBE1D31CEAE79299782B7A4482C21D1ED48EAD643681282282B1B0BF1150C20364431F62224FFD07EF87B9AA4CC4F73FD2284BE1064682DE6851DB21F786D1B6F73654DF04C06FB4F7E1CEF9480392AA851DB4308EFA35A16CF35B59DDE63F6208B90C776BE38CE0139E97C041D508DF432CA1D399910CD19A1A7C65C09A6CA89A5323D205ACC5BC75C312764B0B6599E10C2B8F4DAAAB9B298CC96CF3BADC21E4B797B52AE793B1BF08162DA0D4206FA64D703D100580435854460EE5085BFE241F8E149007EF2A373E2F48E55112AC1394721D0CC272C5C95D3EE9120A3F9F7758E5BBAC508FC5C20665132DEF552A7A65D122AFCFF8B1268ACB094DDB628D39ED6A5A0B86FDC96A7843C3043C083D8C06C8016AC05CC517BAFD15B6BC3EBCE8EE5F4C6D4A6B0722075586C66A769F714AEAEA49D6A0F5F330F8EF78EA55DB2FA5C4D057D3A1E17A92EE224745723BDD8FD7E6EAC10AFE9A0E22B702CA226F694FA079A95375E66B002F1E3C72C3CF63AF044C435A7EC2AB93570912535C40543A3B29AA9C8D07B0434443B32DE9C64B96385390FA0D6DFD80C2528697D253B9E7FD3AC3E5665E1FC0F707B7624FE526E5DF2242CB30740B6A965D82C4CC1F56468C8B43B4070C59F5F13A96E2D46ECCABDEC4702D1E8D57DE00F17200BF581760519FF2CA755AB85B1E6072000B0D7469B36C1D70290512A43F2B492C90C16AA08185C0920EB642054BD4D968396BA5393CA0364224CA7CE9EC77296E0E0108C691BFCE7B312524025B51F62C5A631440A21EAD737D224A9BEB8354C6F7293A710909A4C20E29F2DEFA90DC3AFCCA6608CBF4BD37800A4866A47DDDB7D6CB93E276CAD4C0BC6363B2204B8BDE55B1017181A57B3D8C21A452AC2E90329CF0A57734FD3D566CE07DAAA277390C0ED6A5F9E004F6A21AB57A29EAA2483421C06B7F26ED889E8FB5EDB83D7B323477C71CA2747065C8B6F25319E56CECC956A0F668EDC3667D16D2F511BCF247D1B0A0CB333741C2CDFBDF42E2397B92D582B2E2BAFBE4283F482460BC4EF92285A6C6901FCFD5A4207AB3ED31F0F96F887A9AA3106A11B8631DB062549A389BD938DAF7A78990698F57CD28BA283BD1627E33ADA7A5FED8F99D2916EC6369C3155969B420BD3226C7176EBADE1FDA729F841FDC40A4B71AD06CEE87D22CE513F044193ED05CCC89DA0B02BDCABA46B55077744E5CC67400CD2834299A5117C73ECC94BEC33014AC80A44B213E2454429E87186DE2AD19FCBB82717B410E17D53E0D3DC9E19549440B254C7476566B5FBEF170D9A72103FDECFA8C9734D1288524F5A14180BF729E56A3FAAD46EC1C565401646B60C319676274BCA3348D8ED8AD4CB033AD0E60F46E9326D7905EFD342FF4E62860140FE4EBB0B10A4A50BEAEEBC69454DCCA565480F0A343E69D82DC782356F4082E654245E50A9EB928B14A12C23554AC2BB690CDFF0A780F8B76450C894010C0AB69F32D1172F3A4093EE363285A1BCD8A28B652E02B0AC6AFDFEDC2FC544C70EE216805EE18F90FDB9629B315D2AB7DD2E4B07EEE3813802F131C9E9B1D28E566F9F92F0418B4D5D7C8F04A1B01FD4B311A2AB22B0663DAD07F993FA403F7752E9C4E8EF2E7D5B4015344E43914150AFA4C44E1761CAE104A9DE91BC81B6806264B4F888B7F24AB645F2F51D745C99FE1ABE1E733B8F9A54D6909E247E098D201293C3B70069D683DCE35AF0D91F490B1E439583129F45884ABEC6B25A5F59657C4F74846629746C48F25FBDDEEA64C931E3B07082204E35D3CE3DA543131FF6A284168D245EF1196B5138633030286F64B43B6BAEB12C266881E44F25B5A711553C8B30EB66070A12EE3B9DC78E9E91BCB1F5748F40DEA61B36711588A6A14060921D6776FA84BDA0B585A7CC1A855096763E91AAB01228F7EDD01C53C2EA0B5FB420CABCEC9A1EAB94EE0152A5D315AF370A1C192CC621FEF816C3CB4B9344DB176EB226138787C75280AA061D77ADF001267AD32C146C21CB02B7F0557624335E764B1BA948F81FBF69BD29641060EBB9AC541437900CBF32D8E206695E496E4747430243DD78D80CDD17320EADF225BD0CD0D158F95120AE130EFDF13441BF7578F4DCB3964DD9B507B95B1CBA60230D356C40C51A5D457F92BA19148D6A6E3F459A78A874921CCE17F654B72574398C481BA54976F940C1239CDC045F953EF5943BCBAADD35B9D3CAD3195337C4F28385D61282F97C8E1D63BFA91160F254EA157E7444E98DD5D871B839A9042E5E815BC681D6495EFD3A017806E8C28C4E0627DBF61D1240C12BBE5F08C3CEAD6A0635EF7CA2E816C56284BEE2E8ACEB4B4954B00B44DE006633AC5840A26D415C94A25A88F5B2B7B522669A9E9DD142CDC188B20A9D545E307081602646AFAB9A32B725D66EF158E98432BC2D889086DEC63D7C7B4BDDE13B3187FEECA3488AA1531E606839C04764902F0800955C6833C00FE3CCEDCDBF75F087227BF12F4603253F6177891F37D408D6D27EACCE1512ED2664BC87540510C43A1A89D8AA28C8DDBA97B1BC92DCBA9A68DFF3DC57338DC45F891BA704D17A18DE5E142B09ADD8F97A3A1266B251392C0449561E0D4EA4AD1B84D071D28038D5AAD50BC86CCC3569EDF18E5B24046668106D24767781EE56605BD41B1195D8470581517E3FD8109C0AC24448D57C146CC5D1335B140B9B4286AC0F6D2616030C0458BAD9310AB6EEE683489A1146F7A08E406F00A4CE8F514D4CDDECBD01EA05977F55C3871087B03777D0149BCB81E3A456510014E7AF448E03A36855777464E463261F419870C2E54A63B7E6D1BD9ABAE22855469FB9251394F58329F0225320C52A333CB228BFD6A0EF48420DD3C7C550C22459CFEB0073386DBE032D31C5B75ACD4F8C67AD82C71E7183969D20769A18318A725D1F22ED2BEDF1E9D72C4A8C240943370FED1DE7F23513AEA2AA1D5AEE27AFDAE647F03D98EA7121891227C9485A0A3EB5C72C263998B13CD68367AAC51CAB3CDE76674341A593B05546DE545DD0CEC049C4D445DFA36019F1448AA432ED1C0B70D11DCD69F188F10127FD96984DAEB3D2A7A3790196729BBA73443D109B38A96E394C3419F4E187EA26D474534588F2E9124E52E1001116886D350A2CB3C6CD3881FEBAEF336AC1162C84D0165F14DF8011AAF3587FCDE8D0A19FA260B652D682F57C65C3443AB602FBC27B8A0622C231880B1E62D095E1A4FCABFF880D86C3C4ACEA5946D981369678F4E34FD08A4625D9060363F22526D4D8DB175D6D854A9D15609F704322440AD183FE6FF3F4250213006D91FAC2985BCA13D4FEEA8020C8DC6770F517CDEC026E125C1AA3756A34C8CCCA219B0C214303637010586B8CD51DE2E9A29DD9675411C92540822514F10205820A2D6EA2EF3D742635D81A72AF24CE08C8FF0DC150219CBBC550AA20DD7797EA683ABF681C8042BF242C2799E55EE2C10577E3FDBB413B494447F121969AB56660700E260C31A01B88EDFFF3D8308940E295DC5AAC44CB9CDF672B3F7AD2AC7D7AC1CF668F913A64B8573A60947437E1270658919DAF571289CC5F2E0E7BDFE3E4911582B540B0B36732C5550358BD2738C70721D3C18A5BAE95A3A4DCC202C5634C390F1CB489CC907CB4DCAFE6FCAC299CAC3655BD7DB669DE2AE6AD77F3F78B4A6A154D7463944DA23515D1AB4C75456D601736BECB027B0D544062271A3345E0C9BF9333EB1B02D5038CF771C2EAA1FD6AAF8F40E9F077B907827A31193AEF045F4CB5C24A9F490819644834D01B57E09EC21038A2A701C7D21E0E92C67F3AC8151F9AB1442A9C122AD961DA84679584324F42308C384FE0A844714201EF64C0602EECC06C45FE04BFED8B664930A573B95FD7BDFDEAE6E6CB1ECC8EDA92939A92937C574CA50BFB8E6D4F92D713A4A0C3B9B098C2B45DCCD2544369A923C73DD95B6743D96549F9840781D6B0C1876B2A407640024C6A823292E4688D0A8F83EF689D7BA5A31F2D2A8039E355509F0C6A199D782346FEC75E9D9C4E5BCE1A1E668AB0F383E9B40545A36C4C90F92CCF6032DDFA51EFA03A1A026B53E092842AC49858E71322081457CC6006D81F0C3693F5892FEDBFB32290AA36B1F68199FE655E3BB57479012BC0090E58E4B9D6074A5665996666394ABC542B063174D67C7038172B85B646D330A152FF451DBAAFE2A215D1AFADD79CB9BB785E1B9F400C8DBC9F90E5E2BB54AE66760C0781C1233A33220521096AC8C5094FE49E8D631EA05188328C9732502ABD8A0EE35C8ABD039902A5C8A0C7BBFCB32508A2591731CFB1632F39DF33C2B6F36A6331E3DE35533A3CF7932D417E753ED68F1BEAF651465950B455F292A23F3675DF243043C727775C9BEED7E16789F048AB34A99E9F86DCE589211541DE582AC1B9139B25EA1F7862808DC97C598BBBAE330839320CC36DB8ABA774CADA3711DD41D44F9BD60918C2346704D0B591B76D8FE88C523155E3A7C24B388540C90B3097F4F39141CBDB541DE113F726C102B11A659E403C9AFF464A21AA4A5B3ED8132620623B48121C203B94A32DE0A42FFCB4EAC69FE2D7DE4E144C8C071DE51B1B11111E139AD656552A757DEF878844B97094A633EFE041288E275CE020A935D2F0110D09E7A9720E04FB3FDF8390B73CC92EF9378B802C2371A2FF1F396D95F587D362A615153AC8F687D84367B2C72F24C6A92C9B7119A92B95D69CF4BEE22A40CE1D0354551E2A867C78A71E38EF22F82240846DBC93EF1BA85D152752E9AF9A1ABF3896C1B36A082CF633645EECFE133E60354A05FE5BE0A16E843C8B9DD6A290F0FE1572614362B0E08C9569CCE55ABF8DC821356629F5313019D52496F7BC794BD2B1ED61260FFEA4D9BD07930EE2E66CAC9464710B87A318C6F66242C0764005DB8F9502046582F0A99B13B48A00F8796E846B2649E807D6E4B66A39BA514E07D53B0C5D85AB54BC30969E8CE0AEA37F5305F6F8312207B8505BFFB78DB0A29265D2DB06E0F617E626614371F8C49208B501ACAE806E3385458BCB52DB4D182B8F39ED5E4C2775D0E5882C189B38B4ABC1F49CE55688E6A044AC3717417D96053D0F22491BC3F5D93FA197FC246238864488B5C9A334773B11878AFB48D086CFFC5C8E389EC3AF54BC44F1C53B5273C8851F94D17F841F9D99F4EE8DC0A96640FDBA3B43F3B0174A44A2A8785CA45C1CC4462B4E201D21E3C749AD482B51079C76D44CD0264E074A10658AD6BC803314C0568A1B73699C5566DF80B5A6ADF40E80C5CA3EE7E4A989D4C40E6E043614F8829192308656EE7D81C7EBC53001640345FC9CA1D14CD3D2DBC4CA88E26224162918644A34EBA89408DB7B8B810476E33369014BE8195A7838BEE3BFCF49DE0CDFC19D50B57547879514D53B38CF19CA5456CE9DB44C08FA05E55A626984DAE33703DA1CDF56E277F20E3A9985423C9CCC85287D5BDBA4823CD95AB0D6334DB939FE601A8F1D18B60BE9D010B6D3DC14F6E2DF2524254EB0DF39895F498E4A9C46FE0BF5EE151CA3401D4DB116336F456C64E1B5349109341E94A852861D152FE10C400B98F98A7FE976852077D44765D191593723DED3E17A4DAF288BF54829DACCA038DB37070BE05B968849D8EF747A157AC51F33A31E1934574E6EA09220547E740D4472F70BA581E88D15C8C768F51FB6B006688F34BE85680AEF80B6FF74D1B84545EA641CEEC241110D4157E4BA521235529CED6A43F101D5CD9595E020E01019D819C214962576047D923CFE2EDA821DE59109C845DB58DDBBCDC2B0258CDD0D0B3D7833EF57CB9935F09044D50928B665E6460EA650634C55FE146F2B784331B815C36A9C966C9020C5A2FA9C351385ACE18405CB0EA0039F14F11EC1905D0D273C57E3F8FB218E5B3C5395BC8BECD356D513455FC70B9EC32598E677527F403F9D3A9F27FA14788D6D29F28019F44E80E2DA4787B13E60A43C8E66D81E53C3E42D02FA188C916B515D74C20724B2FBFDF902C720747481A4E268F13087900ACE6C33267CC83635E0B3ADB698D006801F967625E492B2066F6688857CBDF1452598F3319D9ECE68A85C1564A343B52913C8C09441E13C2D5F3BC59D61B624E9D671268708EAE80D74100446F8FE14A32E7DC41C4439793CFF61893B5A4D39883E99E0999541A82AF0DD8B93C07F07A07DA922217ABDDDD481EE8C5A2D6A6D0623AC3F2ED28116EA5C7ADCCC94FF0E201D1C989DC676D4043F33F37C442DD20C9AD459BCB89C2C50B453E6A91B087F3DFC72411C80E05B675A5E9A6129D0D145B3A119147FD00D37D92179B88E3AFE4E77F24A008A3D4B0DC86FBDD2773B53124E4DD5B30B08B048639A75C7094F966E81612D8C0AAF7E4B03E02E2930BD35F8506CED8B491C8038CE8C1160B5B4C11A2E57FDCA4F2B242C7F8B74E18C8C77801F09B277D4FF68FF40605A286801F24E50106005ECB27136EF343597CBDE60B132A57809D1F1FF82310E69BD5A8B851963486C73A681F04473A00E137737672B6E592572EB162422A17752CAD3E4D32076A49125B02B090731648762DD08362853C32A000167E8B32E35A372EA9D6686D82AB35656A4F4E00A6942CC4BA40E468A32C013C4BDFA76C73103E930BA5C75157B62DC3779E779ECC0956EC7758D83604D480590F88FE77B1FAD5AF7472B4905DDE62C59D14757ABD800EEE2D88E496BD3C544397A78A041E9161CB37E3F4E7B6E09C424EF12E9068B898944F53908FE800CAE8C10F625D800E0AFA6A53861E1A1F7C12E2ABA9E0D282EF848BD90C29A90A54AC5EC53BE5CAAD67C5D276E0C69C06862E1A79B9736E19C3AEA178BA8B0AE170BD304C4319DF261910B29B4B569D1B3507BF87468063AB4A53ED2D651DC7C47F6B42CCB57564E99DE5DD438A8069C8967E25DF402B8929EA8244AD67CA9B8818A03067B280A4DE2E1A1A22995BB778C40A704B0CE84A9248C7B6974970513B29AD3E58880E15054BF028AF26CFE30ADED531EAD501F5263F289E6FCCD67E978434296C52F42AA317E06C66DC4008564E971404E17AE00170211943105E89A8625B0B7D76E36768D00CCA75F68679938149075AEBCD04EEAF7C5C0BA4032C60B45515CEEBE0B57AD05C25B6A932ECB6F2ABFB088253039908315629AA975B8D2EA5CD5A1964C231934A310BD222C8896CB870A620293536A4D93E29538AE3EBF091B6EDC84083CD43DDE7DB84440C8DE69079EE11A1B7E354D2E02A326E0FA84821DB48C9B886C10D2EF5286F8DA7206C1CA5EA16852579B0201F8598446D4C0E66EE116507FA2E1F853C11890B32D419E093698B6B2EBAB9C4417178985B8DFE2767BD8624A2088E756EFAE37E98490982288094977A7A2538907653FA78952C133017BC129539300159E75E15C93807F8C8ADBC908C466D720E2A86D8BF9ACFC10263620B9B31DF7EEA587891D693154CF251AF35A2DC01B4A837FB8CA74F1AA744CC53AC3953B68D51F71D14CEFDAF91E3972B2993DE80F83C9E1E401128531A8C17462BA7892CB962BC084C91130CE933A36B2A74AEC1C5BFE498684351996E15CDAAA5C5AB9C767228397135845E82469085104AE5D97523C0C7634D0DDB2D7F49CA6822F6BDDD8AAFC7558A405D1BFC00DC8E80506B0B2A12767C0F905CD1F568991CF277EA51205F04354E80255FCE010B93E822CE08C30448C6F631E6D6339949E100F7AF00357E982378611BD2F17190CE6E2E71A43FB8E03ADAA98D54F78445B2C7C433409ED8CB75D2FC9344C7B2C24A5FC368D5D5C7CB9BEECE94647CC6B65C69DF829EA1EC6DA913052A534990D27756B2BC0AF7A533A945A49B4A3B20668D94254326E18226EC1605B63EF618C1259C81BA5342A20CF8DC64E39F4D7929694B516E64CD03614290D3C033B6B1C0B030171CE4EFA65491D1F376FD2469944524666EA0A28AAD35A48E15EE79E677FE2B38E9B11C1062731935F2513AAA3A5230A75A50AA446700B10C5A326776241100A2155BAE98E8CAB6843116B153E8BAF5D46399CF5F6EA67202B0144E34B0E1BAC6C1AA55FE7CA565E643BC1E15A1C09FC378CE37548D23BE3283A74D6B590CC8D25C64464B83A506D27AEC7F721A911F039AC4671A02564DC266A2EB78D6348A8D100E71F99FA861E499CEFBF200F8865D244BF30BA23EA4706290BD108EF82340B134F9E1ECE3C1226714C15BE72D5D33CB8DA2CF49181DED7BA3055E26D05DD8511D5ACA38192BD1AB7271B653005A6EBD06A3DE983CA28259478B4EAD1D66BA479A92F375E892075329BEA241C4897187F64C370F0171DC5D48CA543F3599FC8A87E7AD5C46306DE61518F471AE909B842601348CE0D92D657015772A7A59E957848263E359EB50DE8CCE2D2E37BC6DD42E4B880221F2B949BBE202A2648F42E9CD9E958D5481BFD186D444320FDB69B249C4EC2A11E4C6868D1E27E0F601163384AB8BA30B063B4D82C70CB26F061B2450AFA9C489A2FA3F11A09EA4CEF28B89DB6C5EF2A23CA9CBE1D980314807ADF85F64B3E627917AA0D05E35687BEE745FE67FB72842B1684587A9C41A8CB8D424E30BA1A7D0A17C6891738AD96B07C9881141FAC9B1C76AB47D0A1D32EE8B3D1B7F38003AB07C7CC89270E004942C3EEBA178E62D0F6EDA1C81B97E3EB2492AC594F710F2414119A481936A5E970F941C7BBD08DE49304D1E446CEF76E52713DD9485EA73CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E");
+        byte[] sk = Hex.decode(
+            "FFFA0806FCF5F30D09FD0009F707FD010A0004FAFAF504F2F3FBF8FAFFF309FAFF1104FDF8010503F7FE0E08FAFF1806EFFEF2FEFCF1FD06050B07FA060C01FAF31300FDFD00070100F9F5110FF8FE0FF709F4FD0307FEFCF60800FF02F2FF0FF9EE07FF0703F002FD03FDF80902FC0B02F7F1F304010407FF0701FEFCFE15FC0503FE0804FE0704F202EF09FBF408070D07F206F307FFF502FDFD07F70404F5F8FB01F704E7F700F40501F7E00CFE080AFEFBFFFFF806FAF606F5FCFCFBFBFB00FD0AFBFFF5FC0B0EF411F4FEFC07020707F9F817FA0A0507FD0508EDF3FBF805FF00F1090607000508FB10EF0504FFFF0301FD0D09EFFE02FFFEF5FCFEF1FF0403F5F3FDFB0506F703FF09FDF3F9F5F7FFFE08110F0201F60701FAFFFA0A0404FE0809FF0AFEF20D09FF0A10F7FC020409F7F003FFFDFCF1F4F707F801F103FFF504090600F50301FA08FCF4F7F90C1310FCF20AF20E0CFA050103FCFA05FDEC0B08FE0502F8F9FD0C03F6FAFFFE01F8FB02FBEFF8FDFD0005030002F9FEF2000603FFFDF8F4F50206FAF9FC0BFE02FBF20BF400FFF90E0001F10FFC07FC12FBFDFEFB0702F8011008FDFF01FC06FF010501FE060A0201FF0302F116F4F8130008F60A100906F90309F7F605F4090C0510FEFAF8010609000A09020C1203F8FB02FFFC04FF0BF803050AFC06F1030808F6FB070000F80D0404FDEFF8F804FF090DF708FA050E02ED04FC0105FAFE080EFA06F706FA01FAF3F80404F905F3FFF0040406030505F808020B0803FF0FFDF8FB00F80102F7FAFDFEF3FAF9EDF0F704F302F10503FAF20302FBF306FAEC05F20A0400FE08F7F90506FBFEFCFFFA0E14080EF4041002F60702FAFCFD0F0B05F7FF080810FF030807FCF5FE020203FDFBFE021209FA05090205F800FB0011FC09FBFA14EC0610040C070A020C0CFE0006F900FFF8FBFE00FDFF0305F50B01FF02FCFE0708FDF7020006000011F800FEFBF7F6F6FBF4F50DFB09030D00060C030200F7FEF3F8070605FEF7FBE907F903010003FEF4F9F50FFBF0FD010810040502010AFDFCF00A0403FB030F0203F6FEFF060CFC010905040BEE0EFE01FFF6F6F508F90C0503050404FB090800FFFF110D0B0111FF0BF405FDF302FBFDFB00F603FD07F4F90102F5FF0908FBFEF901EF050DFB04010603120F05060B06FDF1FAFC0408FA07000E0DFAFDFD0AFCFCF9FBFA0AF201F904F9FAFAF80CF40003F6F9F8020805F709FD08F90BFCF7EFFE050AF901F4F9F4010704FEFA0CFF0506F103FA10FF09090109F90602F7EC03FE0107FF02FCF101FE05F9EEFDFAFC010301FE0202F80304050107F30107EF060100F9EEFCF60603FD02070CFDFCFE0AFDF90EFD03FF00FBF70308F50407F8090D010AEC0603030909FC020908FCFDF8140CFC1510F404FBF20AFCFE0FFB03FCFB0105F8070B00040AFAF6FFF8FB0C09FAFBF806000109FEFE0F050908FEFA02FF06FB03FF01EFF40AF9F502FE030DF4F6F9F4FCFCFBF9FC05FFF90A0204010901F903070C1007F70008EF03FF0DF705FCF8000F0A0312F204F810030D00F0F7F5050008FFF6F9070AF70600FCFDFE00FB03FDFCFC0302050506FDFE04FAFEFC0303F7EF05F3F70BFC06FE00F3FCF3F5F3000703FB07120604FFF9040A0406FBFAFAFB0309040C06000312F901F30200FA00F3F4FE030505FFFEFAF7030D1104FCFC02FCEEF116FD0B010B0EFDFBF904F3FAF3FDF90001030801FDFBF807030706F70304FF16FDF700FD010BFE090DFB0502FF0CFC00F5F00A09FBF604FF02050A04030AF3F505FAFCF809F900FDF00A06EC04F9FB0302FE02F6F60B0707F8F209FCEDFE08FBF709FC0805F904FDFE020AF4F2FD030303FFF8FAFB0CF60BFFFF07FC090613020502F7F2FD02FEF2F2F400020FFCFBF905F4FF03F706FAEEFA03FDFAF607030207F8040FF71101FEFFF50200F3FF02FBF901F50A0B0706FC050900FA000AFFF0F306F802FDF00BF910F202FEFC03070501EDFEFF06F805F7FE04ECE5FC0C00F50B0703000AFAF0FBFEF5070FFBF3F10600040AF3070704FA02FEF80703040107FDFD00100B04FF0402FD0419FDFBF5020901020B060BFEF5FCF207FD01F2F609FCFEFE14FC0D100900FAFBFD0AF802F80B01FD0008FDFB00FC0102F7E9F304FB04FEFCF60AFF0B07FB06FF0D020700EBFF040B00F5F8F0F905FD040807F3FE04FAF2F60AFAFFE70AF7FBF6030D0B01FC05F701FD0BFEFEFE02F70B0101F4FB08F1F600FC09F5FE03FBFD06FF0405FA03FEFCF7F601FD01060800040FF8F4F9FB070EFCF503FDF707F0EE0B05FFFC0703000201FBF9FB03F7FEFB06FFF200FB0004F704FB0904FFFE000603EE0A060BFF0AFBFF08F6FEFAF9F6FFFEFF0BFA0AFBFA08FFFFFDFF010BF60512F9FA01FDEB02FCF9050DFE01EDF5F3FA03041304FBFCFC01010CEB06011304FCFC04F7020BF6F10400FBF6F9FFF60FFEFFFBFF03FFFD03F108FFFCEDF501F5F8FA060305010501FCFE0DFD0600EF0304F907FF0000FB03FEFCFEFF0FFEF8000402FBFCF80D0709040B15FBFDF7050207FF01F8FFF7FA0608F8EFFB030AFC0DFA0DF8FC07F603FF07F408FD050EFBF9F5F5F60403F706FF030C1306FA0DFFFFFD090405020105F7020500FE07F701F608FEFD09F910ED0BF6FCFAEF0B01F4ED0708FB000213020700FAFBFFF9030AF6060A01070712FC0C03FCFFFC060407080104FFFC0904FCFAEFFE03F8FCFB01FBF0FE05FD06FD0F09FDFF03F40504FF00FEFBFDFD0EFBFFFE0B0AF4070C00F4FFF5FA05F10505FEEF00FD0303040103FC050407FC05F90104FD03F501FE07FD0BFBFE07FCF5F805020301000103060902041101FCF8FC00F301F503FD05020601FEFE070409090400030BF60B01F60DEB00FD1803FE0103F70AFFFBF90408F611F7FC0707FE080203F305F8FDFD04FAF5061005070301F607FAFFFD08020AFC02070BEE0907F9FB07FE02050F0AFE0FFCFE07F509FB0C01100306040E02070B0202FC010DFC09F801050102000CF5FDF9FFFB0004FDFB0A040506FAF00607FD0EF10DF5F8FB0408FEF3F4FE12FFF500FA02FFFCEFFDFDFF06FC09FF07051600F7050702F60608F102FBF60CF50400FB010401FE0A00FE0BF50CF809FEFF04EBFE11060501FEFC040B040605FF00000100FD0EFE00FE03F70DF4FAFF0400080AF80310F508EEFCF20AFA030C03000E0308050600F901EAFA0A05F800FAF6F808F803FAFEFF01FA010902F9FDF6F60A0CFEF702F408FAFEFD00F8F3FC05F9F60200FB05FDFEF50A01010D04F908E600050CF407FF0404F5FDFB06FFFEFC0006F604FFF707F809F900090E01FC0C040AFD0C0BFF05040AF30003090EFBFDF602F8F8FD0101060513FBF805FC0B00070AF500F803FDF50702F0FDF9FCFE01FD05F8010E04060000FAFC06FAFB16F6FBFFFF12090805F906FBF9FDF8FFFF1106FEF7100707FC0406FA11FE01FDF5110D0B0DFE01F807F6FF04F4EC00F50502FCE90AF9FC00FEFB150D0008F8F4140F010CFB00FB040F020207F209000BF8F409FFF704F3F7120511F90FF8030904FFFB020900F907F40BF605F607030BFEF301F207F1F801F9F705EEFD0FFE04F004FB0AFB0405FD0000FEF90DFF01FD060606F8F8F60D0AFA01F9FA051C00000B0FF9FE01FC00FD0E04070109FC0507E409F6F601080D02F302FCF702FC0BFEF302FFFF04090105FFF8F6F607FA0A00FFFDF5FC0AF3FAFF02ED09F2FBF7FBFF0CFEFD050D070505F8FBF700FEFEF80005FFFAFEEE0AFE0BFA000906020307F60607F80200FAFE0BF60604FF08F9FBF50606040802FD0004F4F8FCE9130911FE0305FEFEFDF905FFFF0D0B0EFA05000500000406ECFAF8FEF40AFA05F504F9F9010BFA0302FEFDFC07FB02FCF40D04FDFD0803EDFC0F00F7FE06FB090305FCF9FFFA130B0BFBFDF501F7FB03000D02FD0C0300FD0BFFFC13FC04F90E0804FCFBF803FDF7050907FB0702F8F9F8F2050102FC01F40A0614F6FDFDFCF00805FBF803FCF5090EF6FE0900FE04F8FAEE14EC000600FB0801F7FE0408FA0AFE05FDFDF907F5ED0A0C000409FCF305FF03090AFA0401F0FD0A000DFAFFFA02E9FBFE07070501F508FE03F50BF205FC05F50108F405F9FA02F101FAF50B06FC0AEDF4000DFB0D06FCFEFBFFFBF700FD0B07000703E70A0406FA050400080001F00E020D00FDFD0800FEFE04F90300050D05000D090700FC0E03F50304F90007F60D020002FFFE02FE0903010303040706F70800FA0706FEFDF10608FCFEF4F5F1F8F610FBFCF002020702070B0DF3F707FB07FEFB0F06FF0A07000C12EB0FF9FA09F6FDFB0B040AF7110302E9F6FDF0F90A04FF0AF3E6FF12FA010804030AFD1402FF05FA020E040201F502F908F00E030C02F8F9FFFB0201F903F3FFF6000606FBFBFB0204110CFAFCF40202FEECF3FE0003F007FF0AF90302FEF708F00DFD0B0AF906010206130809FB07FBFE0907FD080002F804F2FC0CFBF40301F40607F7FCFAF605FE06FD0DF4FCF60400F4FD0AFCFC0B0C01130807F502FE05F802FFFA0404F908F6FBF802FCFC060505FD00F604FE040802F20100FC070500F40305FE09FDFCF4F903010C010313FBFFFE06F005FB09F8F2F3FF0A05FC0AF6F7F506FA0409F9F900FFF7F307F602EC0CF8060EFB02FEFCF311F910FEF8F0F904090A0BFBFDFF03F8FA0E02F90D07FEFB02F9F206F40308030A00FAF704030204FA05FF0BF600FF0309FC05FEF9E8FEFA05F5F907051405FEF3F5F0FFF5010BFA06F6FD0D0504FEEF000CF7FF04F004050307FF0AFD00FA05FFFD05FC02000E060DFA0005040AFE0A01E1F2FA1508FD0804FFF900030906FCFAEB00FDFCF600010207FD05FB0005FF0206F8F900FEF902F903FC0BFD05F3F9F81306FBFEFFFF02F5F7EDF10FF50D03FFF6FD03FAFC0606F90B020B03FD07F90C0213F609FF07ED10F900F801F6F8FFFD05FD04061005F6090308FCFDF10400F9FE1702F811FDFE05130307FAFF0AFDF5F60907FEF5F9070709EF0009FEF8F8FFFE0A0CF6F30100040807F70D0D07FE0303F7F808F7060808000003F303F1FF01F700F7F70CF8FEFFFF01F70303F50600FFF800FF010AF3F4FAF001F105150C030803EFFDF6FAF3F6F900020C090A08FF0400050606040408030401120AF9F3F505F1020402050E010407FEFCF9FC010008FBFD09F7FCFC04F9F804F604FD0BFB02F407F8FB0702080404F5FE0A040B090305FCFE090C08F90705FEF8FEF8F1F501020DFA0D0504F8F900F9FFFB00010402F6FF0FFAF905EE09FF07FFFA180501F9FBF9FAFD040A0405FD11F00D11FFF900F2FE0406FD0BFDF8040DF2F80801041205FD06EAF8FA10F2FAF6FDF8030CF7F7FFF702E8F510FC070E0706FB04030D0C02F60C0904FC0AF5010700FBF703FAF708F501F7F9F80FFD050E0300F90D03FE1510F0F9F7F7FDFEEC101405020E14EE05F1F50C05FE09F5F602FA04FCFBFD01F003F6FBF6FE01030D05020310F6F8FAFF080906FDF6F6F604EFFA00020704FDF9FDFC07FA07FAFFFE05FFFAFD040C090E18FAF1F40400F3F40103FAF7040BFD1105FB05F7F505071006090BFEF7FBF90FFAF800FCEFFF07FAF8F905080B04F4F9F601F707FD0E01F5FFF3FF0700F4FA04FFFCFDF6FF0A0107FDFFF404F80AFF000402F20A06F90400FF13FFFF08060DF402040511FE05F7EF03FF0206F6FEFB0DFE08FAFA03F3F3FB0CFDF9F804FEFD0D03F9F8F2FDF7F8FD0B0BFAFDFCEDF3F60FF8F807FA01000203F9FF0C0AFF0D00EE07FC02F4FB050B03F608F9F4FC02F5FBF80200E905FBFEFE0BFC050904FEFD060006F7FB01FDFD010FFE04F6FC1207F511050CF8F6F900EBF7030CFA05ED090009030B02FBFC0608FDF60A01F608FC05FAF7F9FE0A040D010B0EFDF90402FBF702FE090FF5F7FD010301FA05FD0206F4FFF4030402FA0BFF1008FC03FE0A0607FCFD0108F60C010B080110110501080506F60708010BFFFA02060D100304FF03F8F503FC09FF08F00304FF01F1FC06010404F8FA0406FBF7000307020DF50C04F9F4FEF500F50105F70AFF100B0F00FC01FC03EB17F9F1F3F5FA080FFC0307FA0900FBFCF1FD0106FB0BFCFF0D0D1806FAF90EF7FDFDFE03FFF7080404FBFCF9FB0AFDFF13FB0502FD00E9F50CFB100800FDFD0FF4FF010307010AFAFC0DF906F4FC0514100B0A03010301F6FA0609010604F90009FA07FC05F7F2F601F3030DFE0A00FAF903F7F603FB05FAF8F9FE080F06F11604F3FCFFFEFE0200FCF8EEFB09030CFD0306F504FCFF070AFE030805080CF405F5F9070416FEF5FA0003F4070203061000FCFC05F9F809F9FFFDF908010BFD0602000D100BF8F8F9FEF6FEFB03FB01F6F70401FAFBFCFFFE0100F800FD0605FDFEF0FB0AFDFC080005FCFCFBFA08F7060205F1F60305020800FE0307FEFB00FA00100CFD060A02F70EFFFD060BFC03FCFA00F9030AFF03FAF7F414FC00FBF605FD03FFF0F802050BFA01F3FDF802FA010DF90107FA05010AF703FF050A0304FFF905F8FE05FDFBFA010302FD0E0800FCFCF8FF0401FEF30102FAF80D09FB0301ECFFF70301F3FC0513FF05F3010F04F70AFFFC060400F1F1F8F802FFF30FFEF8FF01F9F6050402FCFF01FCFBF80703F3100B05FC05FC06FD02FC18020B101102F40604FEF610F005F811FC04FAFBF50E02FFF713EF03F50700F3090301ED02F507FFFDFA0306FA030805FFFD0F0CFF00FAF0FB0609FFFC0C0307FFFD0F03FDF10606FAF900030C061705130C02F60AFF0DFA060304FA0106FDF7FCF404010AEF03F9ED06F7E8F9FDFF070901000105FBFC04FFF8F5FF0306F6F7050700F3FB0608FEF6FDFB0E04F804FEFDEDFC02FE0009FE0108F9F8070506EC1408EFFFFE0DF7F70DFEFDF8F80408F707F6FCF6F10105F80C0210FDF300F3FF0702F9F208FDFD0500FDEFFCFEFE0005000C05FBF90D0308F801F7F9F30302FBFBFC04FC09FB08FA0B00F0F5FD02F90C0506F80904F00B03FBF809FBF7F7FFF6F908060503F5FEFE07FB02F909FF0D0305060601FDFEF8F70007FAF30301F2FCF8EFF1F803F6F8F40D0603080100FDFE09F90002F71002FD01020DFE0105FDF0FF08020403060CEEFE0C06000001FC15FFEEF80FFA04F301FB09F1E90C0401031204F70EF4FE01FFFCF208F8030BFD1405FD05F205FEFA08FF1109ECF8FAF8F904090B0A01CFC6B92DF4B09954DD20371C1E88087D73F0C885A68327486A812A1C9C36DA7E4F5C254B6292FB5C3DB9561B8793D8AE3E1611423AC0A9F8CFC13E1C85FEC6B53E8FE33CAE1CEEC574275C02B17AE78A0018BD4212E087C0901E518796AAA752B6282A7D0DB145AE");
+//        int smlen = 2625;
+        byte[] sm = Hex.decode(
+            "77CA5E0F6AB8586FAE62A68BD7A97DDA9435AF9335EF76BEF113A56D64420A8FDC2669E234AAD20193D2085F9E4B9DFFADA03E33DB4119FABBBCD91B19530793E0BAB71ED5CD61A607055E93FB56F66E8EBEBC6B63020E75579ACF8E2F16E5F6179AFD9E7B20DF0C010C1554791E6D41C224639B9D070AB04F6914E74644B14AEE4C7731675DB2B94237FFF00BFDAD7A070D73BD1DBACEC15094D091A0563F6ECC749FA57AFC4BF8B3009836DC5ED692DEC837953D776076F1D9A0AE98D812758A08F184751D33D29EEFB8868B97E21E115C568BEA8521B60E7D9ECD30CA5DD48EE82FEE169A2A3A1F9EC1366203FF1B1732C1598906F6715B88576EDA2C29FE0D0B4006B03E1B21171E923B5C3775ABBCCD2313186793CA98BA96B932D2561DD915E4B7BBB08B6946C37D71A4077CE9E3868148C33377068A70F1B21C13CDBBBF200F39F52E7A4C2B094F8EFE208CACDA1F7789A4FC471574ABA28BF06CB8940207E06A5B2A0B3CFD2BCBF6421A85D32F5831084A78C914FEDA6D7F1D6977397202CD24A4260B90C11077CA337F10A852A34FBAB455F2B7530E336AF3F2C6AE04B652D7E05FC02264E5C81E0E09FACD01A70F4F66F368D7F308284988BC3E6B4C026F97A65C0B31C1187CD05409C5BA4C22317C3A1CE08EAC8FC25A4D3644B610481E07FE372D19FA622365394E37A6BB61EE38E309736A807A2DD85AC76F88451354FB12AA778E3F05EC69D83AC8EED551070DA0ADC0222E7839D79E2847037A2DB1B2E0F15C376451CE38A11E57896F67D804DA2A4F84BA51308ED69F161A9E08207DA401D9F5EAABDC4F50B4D96357929126A208B0DB29D03CD71A2F203309E41DA4661162A48944F463AA642D67AE9C36FCDCA4F14AF4B136A943B74AD5B8CE6ED6BFDC3755B1750B901584B2C1614A2634D0DAD3F2D54BBE3BFBEE7C07E4D7AE64430364A5A0EE4E4A0D436D569B2DD6C8A121FB33329FD25E2FE7297E939736721E6A2BC37398DD16C7E2CA087182C5958A4D9A21EA3E2BA3754B630BDB7F146F090649ED7A4B1ABB64D58BF1CC54E3FCC8358EA6AF1B2C7622B633AEE12C6664A51F1437368E8D9EC67AB6C2939749A671465EE7A77F9368E145047072664084EAE6233F91010DD5148E2F7A66F69BC5849F80186CE26C0D927BCA38E5BB8A28302946A43CE57872FE5D735793337127E41EAAD931AD96FED22862FC0AEA72569E9B56596B8BC36A186D4D03C87D484111F18A6187CD7522EC926153488DB161D8ED5CB05F144C01ED77C7614FB42FCE566898BC9F5AA9FEF0F7093818A390A22EBA9404EBC3A79FB3543640DF29D2B5B9A601E916DB36CFD66A5067E7FA379A640D12B99D75B67F8ED059B761D3FBBDAC235024D071EAB9A2C6F13D5C6DAE1BF952EEA902A5A25F1FCFFCF8B0ABAFF0B242B48EBFFA6CBA9CE3654673F3AD4CD5E2ADD2F3E630B7FD48B1F7578CEE7D1ADC54360952ED1F1951F3F4ED531BA57BADDBFB7EC437E42A429DEC044EDBA509E5C18BCF8DE6118D6935EE38B3F94C12300D49E9F434F227407F3C7FA1ECED88D08DF04B4A518C508A3B93A3A8C60ADBB8C0B5F3923213CA531007E0CAA77E5361CBBA1CA6E49E81BA87ABE3C07E9E4590635486B44212298CBF9EC48920C0A899CF257421B734377B18141550B3954E23EEF90D6E20FEF7BF04C17DFE8083C0116786DC771DD802A69B2516396A45F6C755E05F228B4C31835F8E46591C80CD10250470B4421FD5C86B97B48C35CFE22721C3A9BA3DB8BCC739AC0CE5D2BE05B8569C0E3AB51CD112E4BF0137D6902E08E5636861E00EF3D58F3B05C63A5ED8286EE075B46EFBF2CE806301C059602FFEACD4CA5E25FC72424D4A982FE4AA6A6B7821AEEA4A4C209E78034CD69E5AEB14D57E5685E0F90BCA8507FF339CC084889541BACDC155FF24CEFBB79FB2496224A06517CD7A5B3B0C829A6ADE5FFDF70BF197E1927D080C20EFADA5BF44764D7062609F62B8C878C9A66063C957837DEF9065E571E12F44AE02E0A023F1DF37E0F5AE70C5F2699654450C112AE96927D2E46AD1F2116885D40D0373BF5B9AC1B07D99B3EBB739EE4B9F9E369F83F5806AE6A528E8E373B512A83E2DE089F3C52C155C282A78D3D8F1BD9EDA5399D48476DE5F2074F4122504E003072BABEC347678E91B921D5B583DB4B4030BDC547E948CDFA07019B180823AF3C88AFB4954F239662614826C26E3D68B12270D0426418D0DD2815F8AA43E6DBC95CAD2B55E0F5438AD46806B3CD319430550317B7C80BC8DECCBCBC651C6AA00EAC9E514FB92188F49E48C74CF2A595D42BDEC4AC6A5229179C16760885168B18D5C5F86CD500BED77F5D4041C4AA244A1679682E9A62BBD5303A9811DA40AB7D5233B13F19A6C30F700C7D5C670CE4C14B7F7CBB6BD593029B4E55D4FC010171FAE640FF6D6FDDDFEA1E1A197B41369580A38FA6979EBA701F4BB00D8F6D4B24E180A532FB235F12003EBA74BBB329D68488BE399EC1066F768204388CAB27D2766E1E5502E3F690DEE57273951BB799F237FC0F1CE7AC404F64DF1238F352D70658755184E027D0BAD33616A821E1C8CA4F281D14A531D34113AD17D971A3D5E6B022725D395AD3F594277690C96877CD96FD1761E226E5D3A98F16F33E590EEF6CFDDF7A9E5D2A2288F4B5195BF3CF923229D617D4D594D5A76BB74E2A5558DEB44CDBAEF71ADDC3A4AF7D1570347FE0F7C715656061D0B8967D5B8147D13AF1A515C1E07EDE157BEBF8F73FD0AAF94B7F3695BADC3D5D81454BA9A19DED20969CB1EA356DAEA186D25EE563CED503EE3B0F4ACC8B59C2071F4D7454ED60083BEB7F33DD9E4975E85AA49A17ADD101DA612EC0D74CFF4B1E6616A8B7A7F7771AB0BFC956152F4679F3864BA08000F232F62F33D3328443505B5CB6F15EF4CDB365B1CBDF0D750DB39F49EF9BF31484CB79CA5F5AD334E7CAFFE7D7AFEC3E821DFEF387AA0677085941522EBF93D2414E9C1365B534555895B635F731E44480556ECD61DF5DD27794209066C7BB7814170E12C6E3EE938B3FF0984F1BACBCAB09DA34D015721B55E156A9276E2AB76C9B80886CA04B825BD140D1AC4C6CE873A4D38AD274F007E989FCA8BC3D05B19475B414A9C093ECA93BBBB45F8A4DD0360807ECFAE3A7DFD1B0AE736B7C17755A404E5FF6DB6B9A61B16A66A08BCE6D6C6AB31917A9EAA5E4715387A634F232CF90CC9AA9E1609F0E567516567EE1FDDC535FC5E5628E811B43C9993FCA6E4241913C2665E4E7A09AF46FF90659CDF0D022A30D253FCCDA2EE8F18714E1869DA71730AF568161AA0417E7A7B338D94BD85F5D6C30A5D0E6C38F5EFAAEC68D7D383D2529F7F696A3A061BE6FAB1EF8F2D7F91C6F10A7898C87BE2679F0CB3EFE5883AE4D954C5913FDED07FDF70A8AE16B2D11E8B0D8A73904388071E49997FF18766592B59EC2443B3C7C5B465508A40559D764CD499D8574992FC571F483B0A3BC8B28BEC0D042585435CA275875E0ED0E9E00BF401D0071769EF7345FF3F7A87DD10204D154422C7F182754DC9AA3CB764EA920329199FDEF60D8D85F5ABA944050A3A4824EC5032B67FFC7C56A591B2A2C3843617AF31B1C1C75E029093BBEC7374C98E79C21A0EFF8DBD15298173D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
 
         doTestKAT(PQCObjectIdentifiers.qTESLA_p_I, pk, sk, seed, msg, sm);
     }
@@ -287,13 +180,18 @@
     public void testCatPIIIVector0()
         throws Exception
     {
-        byte[] seed = Hex.decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
-        int mlen = 33;
+        byte[] seed = Hex
+            .decode("061550234D158C5EC95595FE04EF7A25767F2E24CC2BC479D09D86DC9ABCFDE7056A8C266F9EF97ED08541DBD2E1FFA1");
+//        int mlen = 33;
         byte[] msg = Hex.decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
-        byte[] pk = Arrays.concatenate(Hex.decode("14BE5C398A69D45B0E7A63672036E007981E846337FEE6A01789E7CAFDCD43B59471207B844CCA4240A56CF1DC37941B8669BB2E93B7A84687411425B04ADFDD81011B8DF309F9BC8069B5A0F53292F6F6D1A6B9191CFA8FE8DE932A1403296B3D6492B457849EF361481655174882C2A899482AC9236901E094BE6BE9113E9EB7B18E0018C46CAB21F14502C63553D383BDC53CA1064CE6BC6704EA05161F0C9071428340DD0342F66F0422AAB29950ED8A95363404759A8E08677223A08806814EBC824D40D89F7560175631533BCA8DF81DCED3828EA539BE87328C14183C91A6C24E496227C6F6A98197A8ECE46D5912D99F72D17651F5850901CF4C060B4D1E5C860C12FA479342652B54D5E5F58AC2449C6248483B900E8F7A2654DF3ECD76453450BC4162FD1AA9A36B6468C0E37A18F54A572E9E35C2047513466BE99BCBCAA678E0FBE657488CC110A27393BEC4841A4E7B784DAC3F840DA102438041AEC6BEC4AC261FE118994D38CC1461927D3C08AA4B3A22E2393FC49B5A5C408182FF943C1F59B143BF0F11420C78FC29162F9E2EB81277FD7DD8779F3FCC9FDB0E6065B1C41272709A716DC204C51B47533CE1B3BAE15EB050073E878F70696B92E6C20870E8F3C290CE4812E00C1341D4E36A9ED8BB974EF13A8CECD32F4D36A1E912CBC1A65458454BF58A874245EEEBB41F7FE6D30CAF412010D91F1D455F904459FC9E259C812D584EAAAA200395D23868E838DC7F78C1CFB23BAC26788C10A0DEC2AE358DF79980543B274C096D194D1750CCB74C0DCBE3C30B50B74C8FF33EC1C0C93B65747A01A85D56B17FC2F0CF091E7D98979028FC0151E3E3A3BAE05D0FA5BB9FB9B62F031E8DD9D597E5758DC1632B6040D72B0D65B1C566AB9F846C547C8809E678D86EAE55280F2E365C0B6539D3108763D7D6A3821992B9F0052152D8A4469CFA02B332DA1A41A340452D481660DDD8B2107FF8968113DAE0C4BDAEDB087B9868AC0E3DA8E8B7F54D5FA4EC26C94B18F1919E4E5181079A8C4DD2C2F6FB416D302FC298D943EB43A6B588EF9C9498036A0D59C85FB37FC4EA9C8084DA4C25193330359483EF8D9603B38E9A7107478762557A590D1DC1D07FCEFC9EC969F6811A92B69B184F8E605A489D0872232702464AC8616BF9EBD7ED82657541C3D96C3922B7E2C31A7722CA36B4DDA4CB775071AED78E2829241C9E20096BD64AA69FBB47571BAF11EF42918BC0606486AD5838EA8794B2519A5289C48517AABA1E51217C49718DE8040875D1E1049B0372A2C307E2EF6E43503314540D3C0D054ABFDC85FC8BB4E5B48FF071F0FCFE3BAD96E02108302EFECD21380FE59E1038D7E7D79EBFBC4DEB14CFD1114367EA1E64DD950A84B11E9C1815CA4AD3971EB20752EEF63E2F2A188277A93CA022C1AC3E0CDBE94110240F3D7FA1264385CA9B997BF24C086897D8B23E585D3BFA4F8B611C578F789BE382502A2A14D2CFD899842FC88B300D5E929DCF9388E9F60782DE92F344598901225B94B48C7CDDA04C5141A7296C3046173161A8B902FA9C830FAFD04CA53F62B11403E1D96906ECDDD190C77DDA4350F2FC8A33AF7C5C9BE9DB0C73F726F2730D27A32B85845032A20BFC5454E8C8E3A4501C7A04B5AE82140A65511CF68CBBA3A82F18448B84C0E576BDCC94C2482C08B95E03A66ED9D0206DAB74C32E160A16BCCE9F699AC3E9872BE87774168891C148314C1500B506B6272C87AFA7034899BA202E66DC224A60F824E9FF51C846D651C5FF5215F828093458D60BA1488FE84494A2D26F5BE5A484AE6B57CA2E5502C101A1C4C828A70AC1E7AD3D71CF8E5A4847A56F3D110D18FD501A0A60AF0950518ADD4008540799CC1D1D66DF821D92877B80A6C58780EC34B1C97CBE9ADDF79F923ACD142FBBDF39293A0ECB17F2424C2AEB5714710D2749DBC02351656F54B5268E114B549D1111CE4BA441974AC989AD24B5E1B07C2A74802F1C09014C752A898E0EE9D51485E370644988FC3DC0A531EB6609962AE8C583179833700E944BC002394A5A93F11B69D9F90E5EA56C139873241A8EA824C8ADA018A5A4247D8C46BCE6768FC310C2C4C3E6D3A91158A2F21B37AAE306860C60B890B4EA1F2A3ACBA3BA2D256031E0BC8683A41F454DD5E4929C6201C639A9C6BC26628ABF45B1540BB2F94D0D5D7515B8A553A0CBE3504F76EB5B5A3EA8FC6E115807303199A5ED93E1170F021CD9F48D5E7C260348754E4079926C69D6B911FCB5BA642A68D81477C4631A089037CF95C623C52BB59D697300E0919307E21CF9B0116C009FB38ED08380E2E3C26320A29BC2A01379E202BA115CEB0D61C299F71DD8E153AF3EBDEC1D0187836F6771FB71923CE6E1ED3265492E4728CCD62267B1D49F84200BC094769860C3409F5B07C55CA5A046BA1089CF0685B6BA6804335E8F196FBDCD01174EED8B35338C44CA3B666A5E7620C822E1D14449763FEBBA08840D446987A9308C02F307BF645F00A63888101535C3E256147BFE3A1B93992FA132B95CF3DA5CA76EBC1B1118D4469274628C047C647E76E69A21DA235B837F0EC5DB7A684E28FA38229704E38C012B82F5FC2329B7B8C32643D606C923D4D1CF945361A4E86F637107DD98DDE0767BE55991E42F422D7136CB12989434C2E2D73F9D6089AEAD7B002DA0C823877F102DAF2D827488BC5047F932828EC1598277A68C0A043EE333C25E8BA09B513B71053136C43AC637DE604A801B5D031053957F2AAFABE3B322466393C38B3E79FD3883D4B26952579C7FBF27BA3161BE47EC05C36274C486B28069B85F3A1CCC0729B67D4065CFCAF822314FE481A97410EECB6E629196CA2B4936FDABE460F9323C541220F6B81097DE9F2243598577EE8A931E02A7A4C99019C95A8D981B0DFD84E14FFD36651748B9B39358809587AA66ACE6F700727B16649545E3EF19A88C5840BD1993ABF60DAA5E75F3A8479EEEE400DACE2B9B56A010F5B704A45A9CF67123F3B72E00E3218B08D01EC3E62CA297B95777993D30E7ECEBB84E2C89F3008AABC787E2F463A0E8AD93708B441791389B447999044F2C297F300A09955F7FDE9312696728C4D57C41CD0D6DD171E1DC8ED13C71FEBDCC6E11FAF5661A4762AF1E481D290A99B714A5A9943210F686DDC15F52135DA1C324FE13AB500EF8990A7DA916D36704C0A2770172706F76559377B6B698235A2CA0950603E3664C2B18139E964F8A65EEDF920766960D7AF2C454EA459C8C8A8A0BAC144026EF3E07655E0468FDB0A070D02766ECE413F0CC6B0E8AB6141DCEC4B7176F747CA888BDE34072025FC5903954033E8C2E238C863DF874AE2352E69D6481D3EBA028CC82DAEED636307E25019613B69BFB3C3E789889F12120D6F82E6F147199D426448FB133DA20E2EA35916831652017E7D549BDCF3B2847457021F10F9EECAFE9D8F6B603AAB7C6D66204555BBA86C30F6411720BA1387C7420E39FAE827F454B851B1B6D7FF850338B9F6621F1C00D0160A61B1A5B2C1F416C6B3BF455A4C3E675E5C2CDFCE355C08513A06691497DD1A317DA84C14CC5FCBC8FF1F49DCD820117CD4E11FB545D5503BA4BD4B479A76FC62A30D3202E22643C4151874AD5A4394277246F72BD4F5D09C98112BAE11B037B00125E1BD1E20BA234DD5400A72A63342CDB726EC5AF987CA91330D77F3BD4CEAC2FFAD7579E8112C81758D9140FFD0AFAE0B6BB60CB78F40FDAD784F9085E2134F778FFA2FF71121D9D07E48DBF28864052D851831DF4EB89BD661882C4207E3F5685068D89D0DFD3F73C8072C946125A64B6EBF957C94D4A04409EF9B17FB5A1A7916C4610CBFED3474E563387B69793B3528650FD68C741E260A2CC5D2A229FB00CF4DADC7ABF168EF983F613F2B508A8C465171035D8FF0616BC319BD2B98C88C5728EF92086E4AD316247A199F6A123AFBAC048ACB1E0DAF62A0E17730674F61A801EFC2E6505E9E6029C862C29E79FBEDDEE69F8FCAB716723D6A99B8B091216259185A65C5A9461413EF8616B9102F0BB6FE68E014C62290345C2C89E77511D9746052A188A41AB3E1E141684B38020051EF0E4EE77C979A7B7EE08B91338ED742E30E91250741BE8D1EA6B4B105F428528506B646A027E82711C56D7503049021809C050E2FF3FF1001AEA49FAD40AF255713D431DD7BF6BB62B15B3F22EE53B4FECC7E865B3881A15488C18AC052C1DD90F21637D1E06034649DC8CED834F4A95C511AC19815C86BCE452CE0A9C15BA0B7E98E92280C9F8C7F2260CCE46687B46702F2EC81926B3A4BC9BF7C6E28247B5E248C231B84CCD95131D63DFCD33652D2C1B89025EC954F6C9987412F707846201BA2A07D80AEF7612E2CEA5B473980380736076583D140C71BAFF5C46023D03AFBEDF45132E16D02FA0152A0505DD9E20A4624CFCDB09737E08DD5FD1CF20C1896B7660163B564FCE0CC7B1339D74138550CCED7C7B950CAE55706EDAD6BFCF3C9447AD3D4D2CDE6B952702C0C1E1C3E3272959EC3F82233562959608B8DD7501D1BE022326331AC674D71C01883A216D8801880D14D2934A2BFF3087D3B4E28851C86180F474A4937DCCD901F3BADB1148946EB6FD8E50CAFA588749AA6987B2DB6EC838EA3712D09DF128C6D6810079C2EF26CF4F2082CC539A7B569455AD124E42347195C641B4291C6AE909119DA25E8C99B966B2EB3B009208971C159E4B03DEFD15052E53C398952988548F8D2470371D4142E80D074877E115735135478DA878947939BFB19D4B8410B7AE39B6EC7A7AB040BE4113848B806834DA4DFE8FBCA104D061D884C491DDBE44EF645DF53D89E468884C8692C8F5AFE3CAC08ABE231E721D93F76234604207D1B243FE4C9E69739D49E69ED4E68BB249EA4AE384A811B8005823BB893E8504DB7F0257A13F6DA14DFED71AC59FDB02603D11968F8631F4BC4F0548F51807F22CEB49A932AA154A9CB31318CB2C86413589177E97FB308092EFA08AAF8C4CF0316ABCC8BBB55DA9410721B5B90DFDE4682496CB040240B900A00BDF7D3E19796FF6378333D95D609EADF839E7E9BB05636C5B68DAB394E059844FDBB26DB9013F34FFAA96C88799A3B3CC8F192442A03FEA9F69E5E000D0B2E1EFD05B4D3953C63C0B71E7EED50A97F725542DB6B0D451C64971A9A600CDD5AA8EC6252E2AE8F73C16CADC0786CD970262780642FA8C320896980D596887B76476307A41820A008FBFDA15EE8126027B188092C4AEA08A734558DC1F680D8CE12C25F315421B7CB353C7D5363F06CCCB2806045E2663716188F439A9F654F8736D06BD0F98E1B53F5A5B1444F0CF43F1672DDCCA7AEB76ED2061124B5870C62C4AA66C03EE94DE1615627747D1D930A8DF250E9351FC8CB88E451FEAB0550DC351111F3DFBCA5B58F71A635F68F101F9FC5208084E75C4FD5E7C5664452A9733E481FEC874D17E85DF09FEC044B0ED303930F20600BCE3A9715654A13BC9194822E1008256B8482C6FDDA2E5A342D8CD120638152D48D7F3E25AA7589599B82E4AD175C5C4B09261337EA014AFF273F026F27019E2BC917CFDE737179BF8B8D9F563D4275F9BAA21D17AB16A5B8B93505F6C95BE36CD7277401EBAD6EFA0303678432227E125E6759270593C4903A7C8ABF8F7246E9A070A01C10BAE9168E261C1FCF8D8AE558A3050358F1D389445181A6F1BAE1685B66D1EA41B294CEDED9458325FEC7C0291762DD7CE6219B1D1615082C472CAEB7D616BFE1756622F212AFDAB6A0A597F03A7779ACAA95A4E385E36D4288A72123ED083BD8D9B939F6CA155E29451C81698B8236BAB70127029A258A6B0D6434F02CCB26A846457440990A72EC9EF332851197B6325E2D8499B8C70057F0C8BA6763404EACB43489E2F301B233FD66FE7D47B199A93CC355E1CFC350754D09104938A17F287C88E916AB3E45CE0A44987385E0E56B923BAC0D80EFE37779E570C20470B9B019A60C471D425437F8665B15D3287D02D4E93964778225A1428B579F4C68DA0B3BDE33C2A5014B34D93DA567BF52D11F530E032B0388F8964BBF09AA58FA6B64F4B463E2C3735DA10519312E98D5A55A01A326E6DCBA3F036AEC91206B089E3A1C19785C582263EF808860C79311A10E6E3F77C70FE96A0FE25C85C08A1CD43FF0D9A5447CC7601EBBB429D030AD2E28110258DAF68B9F90C92D4ADEBD0065CC9D1B090F57C93F8C773B913F8C150C6337FB6B32E4216105C91F13188A65E870DC3CCF8EBE18C19865D98645CC4CA866B04E55D8CCB25F6C3AB52DB299BF304D34EBD68C184D552C899DC57A03C783A46E01A8F82AAA35296930A687E6A99250641DC62E9A02D7CA5C96174198753BBE09C1A2A36F09B1A7CA42A787C16D2FB9BF739CCDEFED80CE40BE9EADB69DDEF02B6860A240E4A216A4D50A917D523C9CE43C26B56E4F87B7EAA8960283117D5B396989719476036E7C9DCCB3277192A502177F2FD5A24403C747D70E26B7D824988369517631223EC3B6D7233001020A54C2414C0C4FE3CF37057C59A4B2268197E17D097E24945D95508F23B10794DC904684E33203A6B074A466AFFB3AFA48A830EE6F1AE1DF35C703D5910AC3E71EC4805E402C2743D3BC9D824B02D0760525B5F738196A7E575D921331A61D2F5BD838A3A8A287E2718E538A22D57525AF21854A1B1F0C8044C756618ACAE9B294D5E36D84F720303600C8241CA2C117726CF6E5550DE534892A14115F7920491B9A54A84BE3C2A0E168D3E9ECF1BE3E7E7CFEC206CB22D97273A830514FAE42FF5B2D7FC1589E097217C16D658FFC1C08FA3FB37445B75A3AAEB0CFE91D234037DF9C824EFCB05034C1C2C89949032E90C760D038DDD894205EA12B1E6033F185AE335E0FFA129C19C435490DF53EC30C1B6B7261D1FB89E5427B8D0103BC8D8370ABC42A9B0CDC301D4B9827D27D2FFD361C93976234BC699108DB4B540EF89329CAC0933C1274487C80E4E8F128F54E5EDEC60A102585F408E890B52E590393318359EBCB710586261EDC1E66646B514660D006A2C126CBEFC21EC431D1E99D706BEAC7EC79D06A909D02AD7A694C672F13A16FEB9D51C3F11465A51512F14D23413CD10D7F9D47FAC45B751086E084CB1CBC39E64994C9696B0A3337B9A6E07151E85D91B46AB81E716798604FAEA1830C8EA55082C7F6206FC50ED18FA013934665F21233485597FB9767663E548E60A07ED62AB3A704D7950612032ACF4F811157B292F6FEA408F56CD1EFE1A6D3B8E616EBFE756F5055817F857073E69709B538CD7E863020FD0139FBA6AB3343B88E8A2C25EF716091E41F7B6022F3C06C148F2E819106CA03FA29A6B50E18BD16C347F7C3BBB4A331B98A4DDE7FFD3E6B7A70933844EF37A4F576B14FED215FA4747145CD5008CF90A996CCB7CCABE698566B97A4EF00B605792EF99E260CB9A5D86A0B153E6991A8720457A003003DB44D39EBD567FE9CCF84CCD6C59A765C272A15C683082282D3B103D556FC6160F4126FBC57FED0C183515D5C423229F1805BD2868E3AD8B901455CF8C7A1D2DD6225277C613ABCB6B6F3105C423D502669F729FFED2B857C7979C21CB74403024F3141534294AAF0C5B9C9E4ECD542BF31860009C24E4746D0A3ADB396B91F362F4C3FB5DEB7AEDE2B470487F2EACB3649264AF0C8154A3DC085497EB95DD60D4E6D8F82321BC84945FEE29CC232005E4506127A26FF094F11AB5E63FE9A9F328E817490907FB60DEC2CA22CB435F5562BE22A376A75C0965FF209C7CFEF14F3483D890FE0B659CAD716497C5377EFEE0BC2018581FE10A580A76BE0430A0015F4172DD42E1DB4684ECD7E43C6AB7F18F999591050F46FBC6E92E11A59167423B52D0F820267D604AD671732ABB111CC0AE8C4ED2899B070C7D3DC7D68DFA2154D6906964901414788D29C7F3501B61133E0DA145FD88B0487DA2B9466C988E30BFA9634D35646A81071D3FC599CFB0DFDAAC0D1EC9605BBFE4158E0F63D542728CE577AE346DED77D6C3202808E653E1D4F11461E764DE863E4D2FD87F715630B39C8DD867580572D8C4970AC640022B0CB82897D3779156ED9A10734CCA402EF088DAD77A12242F003453512399794C688E9347CE657424738B335C49D59E7AA2B422B327849484418CE24C45621E6C07D1D5E816D3A6D6A9A65DDB4D1BB04EAA72F37C7B4DB39EA71B99192D7587A5BBF51328412797E1902999C4579903A302F9831EDBEBB3795DD1D09B288CC1A257C4349EAF7E334E9B966C34699B6685BC80DFC1AA1A5DE83306195562AC896840516973777AF437EAADF751714892D84B63517E187731C34A11402204438CF587B8CC19DCF05C826049C0E3304C5A0E3029FC16D2F8B3DA03C2B169A6F6D6A44ED0F35826EBCE85BE7F4DBEBF1302CB042E0AFC3A349FFF98B50C8993FC24131B3DA02B5DC1155DF5FA3CF0B884D9F0892E26B1FD140BA03C4F877BB9C58A99FEB3322F26D5272CA6D000A83BADFE9585385078CAC90E7301F2AFB393147327793BD0A4DC2578846A4E7D69C0F5419D272F1F022DAAE4CF662E9CF59B972AE8A20FA21845A3A5009C23402EA65C2E3DE16209260909A6CAC11FC5A05F61EB20D2C888E3091E0044B80FEB0E3B96292E6AD661FA9B6F7ED9915538D42D44F182CF103956C59C3C6140606DC18F93BE449DC1E2F20986BE14D67EE1BD0003BB18181CB441D070D74EB4E26F9BAC32E64C1EEA97A26C88BD264FDF1A12EC31FD0553E66478425E453B82E0C13DA9DEDA7328C28C278765D1D3B022B2B5B3FB7CEE42C254811E85105D39ACE8E2B64F0BCCEF03571039B7FD9CC2886CDD057EEB9C8C5A47122FBC9B192843B861433113DCAED72885B1F8644A68D21444E59A4ED3E474B1753B4CE034E9E6DBDB4F41CA00BF45A8D1F0B229642082F87D266EA02A972C0D314276236CA5F2013E15092DE373C6F144BE76002843C2C0EE081EBA4A4C59A20FE826F4BD0FCA63C9C06B3DFAB222221D1BD4CBB603AEA2DBFBD6BC722CF479709788D3F518A6D0B542772208E0478F19D00276057D4D7A3C7C391D10C715333011096D1CB7A3646AE5168E3085FC987A0729FCE0D35808667C4F76C7FCB2410BDD80126E56A3C5C72522489D2593F95A862BD5803A0CB0A4843AFFF2400825D41F421B5E7E29D68737273A5645C2142CDA30AD09F0ACB3B720B9C28732C9291442F9502861197CABE46753797A57ED183983F4D940C80043702EB0068F74C514A3A9DEE904EC3854E91E6C33C5BE6770F57ED0540AED19DE64D3304B53EF5F0120E96A4DB66CB4FFC9B3FA79B8D29966AA2CD4522E84DB0DA115EEB79C1457341729C4E91768E92F0FDB0B7E2DC0725E6A98409A73C6400BA4180B4F00C8701090D34017B3C8CB3A6308BB44E843416FD6ECE1773FFDFCA953835945B67BFCCD563A47044F238F00E1CC82095B649BE6176F7A67F5FE8032EB8F155428C7C6635EEDE69176847AB40D2E60BAEB3018395A60BC3AE9AE32B06153A8AAB22FC2C944D8887E0B8A6BE6E3DD39CCE2E0C85C38B8EFD0A08B06F941A86E17800A88CC036021F9D023014C80DC9CA772079A2CB23EAC1F8D78B284D15CEDE7E5367546B1D71CC0332F09ED4BE7C9F0E62F5A80FF462D477AC30D282269A433E36508C6495BEBE3115ED517A24270F048216508D8C20887CF524AC40980E3766E028D553EB34AB7DEC4CB96A940DD15218AA17B8444B4934DE09F0C87D89923B7227F5AEBAE3AA6146B3BFA1A790B3C98CB8F8F711D349E5A524EA9DC5128862AB31F7AF61BE71CE9893044B0770877258685260D5E0AF6B69FF034212B72134B17F4BF2948BD12BB087E82416595A4E92AEFDC505956A449239927BA368220336CE8CE8928A1B997BEB69A42817C44C13CA0DE4FF65FCD100197338877DCA198D7C4DF1484AEECB4107833407751904A83BF8F39269A8EDC405E285E2854B84DA2B200371E126CEDA464B0600EBC9A89BA0A36EAA063F5F442440E669A48A76CC5F9A63105D06FB801B3968CB3BCF5C4C7263088A1712E1777870C5A3240A85500E3ED0B6BA588096C082D07049053DBC16DAAE9828F1B0576E5B003927AF8FD04CA0E38E7BD50FF1CFDB59E794F46520AE42270189176750DE56BC996AF0D47581A18CDFB469717E8861BE49807F38AE1ACCE1036B695D8F67AD2C7963951646EA6D792723894B70ED65368141A4C09CEC5A43812359E0ED2AF1FCB2198FB0C129D2E60EB7F3384460BF79A0F94FA20C97D602008D1408100D37B1563437DB470037C57E2E21D0C011A618E350C203CF459A3F8FE514236183A200BC90658AFEE37F0B94AB329A00361827B7C08D2F2A3B7153C9568426DC1D44D1E07EBF2BC07BCEFC31B5519690C4FD6758DC0AC5FE0E5CFBAF3641267503E0DFB689C4463567996A285FADB4D58F437A66455D5D38A648083FE6E5E0568A03274306D5BC413F1AF7DAAB5C283A22F0AE3498211EE4AD2680A3DE270F89F9F40A53329F91A6E0947244C6181C155A4E2E74C770A684B3B3F8C6E456F2E43F4EB2D0D3C5B07390D9E011E8D2D20E0842A84912185298022B627E87D7D04154960486150C9A721A750A035F601C701C7984296B71ED3680FEE28E5A47D4A78AF3142D4E034D26C431377C81D2201B87D52E57075A1D908A2E0200F9D16B602300A2316A19A1F9996A441D46BF4D1701335F742A8785CE099A948E1A8A12B867F8358BDB7FE440DCCC234A1A81C23D6CAD1D8129D815CD4B4E6073BAC8A78F6CCBE8CD0ABE28D65F309103DEB98F3FBAE877AFBB549D44E2399B8A07C4544985D9C88EA99C679F6177091ED64C374CBB54940771A59E4FB36D241F0764756F29428600961A0FB52E1E6B86012E74654B04337616067BA23B8BBB76C922C7B224614179F3199C007A7FCF534051B40A25DB8B20921C8DF46993D1D0BCD2D175DDB5F04905E9EA93C175754821DD2E07A38F06072A43ADA69A441EFE77AAC95327B869570970E8260F36401C4C7401077ACF0C2F7D3BC23B846BCE0D61F50911CDCFE1A11707565E4D52BCA7B78C61D96296D29B30E483E6C1EEA2534A783439E2D23740730092B170215427A78E079453565EFFA6EBD79EF6D03A39ABA3DC4C6165FA01DCC81E354FB986A09E543488C61F746F6BD7449666DA0D2A267C70C22B52E79E8A5270045EBDBC36A06E6F7AEB85635E91998F2DEC2D8F9FCDD5C955EE34CF4D4E039D59B9171AF084A7E1537C42C8A82B6FEBF3A528165DC465259E1CF7668F33A8D3E03927983AC29B1D052D4E12918A9FFB56C803A10ACDA79F00EE70967776D285CB7A1CC4D99F96B54B2B33429EAE0BF84FC914C182FFE66796DE6F06320B68B398DD92D757B80B4292141D6ABAD1A62394E0E5B71DE09588778741559E37A4FFC2780FCA13284C4BA15E0CA6CBBE2925C448D9D0C8C07699F30CDECC811707B4314C94CC6C2EB1A80346CB4338906CC013B532249260119E518227EE8149A7EB04C349CF4BADA1FE48D38B33B52648B59DB2A85F88C46B61BC37A3245C2B87566231A5AE26D645124993E8CB3FE1604C8CFE35737A654A248DAA21A20760DA210FFCC343B316FEA913658ADD411AE53EA837DA8B2E79DC92CAC6B4986543F5255206B807B9FBE07B56B3E211E337D71EBC1EF53A5C5E51EF044384995C87E1DEEB1DB610EAAF1A1EA866BC8B2E9560C2CCF522333E699E91C92CB4C9A615B967D2D4E992B1A8170428151AD7847849D713B41AF5D44802BECFA50F7DBCA626C5F60C5E9BAAAC67109570175944384E65AA6B624A73034F1C4459624A3D826E97848A0339417AC185B4DF9144E9EF9B691080E20B9BE8452D4633D106E65146EA35F7B12A9105A986FB8B68E31B301F8DB3ABB804244DC01C76E52C4C6972DFACDA999D48AF3561E849B10F00D06916172F27A490BFEF1E8D0FF3CB21F013409A7C1F2CE475B62CC5A86E527BBF369FBCF605BA881401DF460E0EDE1347FB49BBCCD01A0E12D7D97C771F30768E8AEDA387E3AE6E01615EB00F34734AC6DD320C8D97DDBC8F71D64438213D17147B9A0E188BF1C09D1686809F383384BB7101DCA45137C07632D5BF488013EA2D7FBEC4B2538B491081F90C47DA522D919EBCB87D83D941D06832A51C3D51F3439ABD64DB94EF540B86F627AFAB8779E0A52DA8ADC5285CD427965E223F2ECFBEEF8E41540381DA43D8A754B68F78D644A020BBFD295836E854B785CBC0B781CEA596D12ED21B0423368DF51A4252B0E2C4E73BA50841E36B8712A31166C935A4E41F6BBDDC2FD215496F1778C57C233C45B721069A5316D152F98689AE2A9383C9E7453B463F22E21BC78940944D9DF3301136FD829EA11E33A17B22505A2E0A34833E88B28650F67332D244E99587D8A528F891B1AA55A0A54FBE434E8815BB466D0CD0E2663892A0571775D1E513785CDC9938913309B15088C4D90A94C670CA65264791909EA3ACD5EEB929012DF6961400A53E0893EDBE432B0C595FB9CDC288523E70271AF36696A1D2E78DB6D7D3887A17F7C02D277185E7DA1521B7A45BA29AFB235048A403D86DAA6F9289F1612C440263A239254C219E695C402C56011D8AA75C163493B9BD37264A7F452208422988220A1D4A075AEF58E86A778149CB17D094BBDD10A61F94362295EB0D543411DCC6E5FDEED7F400CDD5EBC1BF8F7058B9FDFA71200157A10051D7E1753F92C14C0AC15198D82D7CAAC166B684F719E5F99730CC881A62AAE7351D0DA0C0052EE2D4BCD6D1AE038D0EF496F9F453DE414DBD5207EC64B5591291373D401E13A6AA04C4FF71D46E6657A6E888B9F480B340B160477CE7637816174BB060AF9039409DCC68950C74143080CA9B3BCCECE0DF5FD9A2F42FFD515C82606B324C997F0DB44653A83084A7C4EDB0A2E9D7C8630F5ECEE75313CC5FFB722A9F4FC45FCBEF0CA06384C22C1BD468A5CE5469369A50882254F79A63600B6D17360A318BD243050255A1B05921192D6A69211C26C68AFBEB94FB8C9CF9BE2068A5B0C9507BD2EFF6285473FC6A81AB96002E75792270E39EBC1C0258A63863E7CC9E58D8BF24D1E45B76E2C311F0F7420AB3156C4E7D5326045D861C3C2A25E344E41E11C61254462436A2C1D352990A3DA220E41B23017ABD92FD0C414C08C7C0F9754476D771EF6D542D09A61D025C34540BB18F143E80FCD41EA4A2B21E626E11AC84D1DFFCFA69DF901711FE916EEC4F8F0D131B7762F037BCCCB5DD818CF3292A17EBB0A83B02E8BE29827CB97C68025B6E36767762A854A334D07D46684A530332240BE0BA2B44F48FBDE026D1FD33D22650A2DA2331CAF89E4604A6ED8E262E31E23955D6660DDD5C028E841599E56210008F93D18EA746A8B0964037958B3F4CAB95CAE5201F664A68D2FBBFF221000AAFA138D8758FADD641ADD4931C261E218C7947BD7E79E7365E056BEF562A66299DFD2217C68F22568E8E64BBC228601E8D9915EEE3272E31DDB00F156539CC2C8964600608EC3D87CA3248EBB2CB5AE59C05C1294C22E5E2A22282BD7BC71AAEC0DE44D9FBF542A5B15429A95B6A1FA6EC76E171449EB682795F89D326227B05201652B89162C3035A368BF32DBEC955CD854DEED3E74B313ACA18508EBF16C8562A9E6CA624E1134589C3849B2E35AE1C569E736D7B6F11FF136AA6E61534D6EB96866139E7967102A095D4D9FA0B89817A156DAE43671818759A159BA1D3D894A7C9640091BF18B15B3419B0410A00ECBF11E70015AF7A3E0284547091BCFD92275FB2878FF78ABA5952741FE5EF76DCB5986B403945E9BBE3D681415F6FB106B684A88BBBE0E8311C1DBE95236846427F20583CE4F50C1E40750E074036EA7883184A4B1BF1C480141A082F62C112771B3C33517F2CD20B5E869F1D996BD7F62013F29BF8FD8C6A0324E1ED064D9B207D6A8434B104F4335806092B80EDF84D4A39B4173183A86317B77B0D1D13927251185A91896CC313775D466CCDF52250A6D36EDFB97419225F544BF0F0F179FD0E0EAACBF5DD87B76A31603BE31627EB12EF466EACAD5B3BDE7385F6AE1C4390D6248EB03A689DF78D4D63B53BCEE6117139ADF8AE3AD2249FD181F6554003999BC98F571B0B39930BAD526C1C0F1AF3F358B4F1228FF03FA69A8262C4AEA024B3E4984FC3DF5CA639410A70947216B0A82476E8BF4ADF0A52F18EBC0F79000B0854479349D5ADF2276C3FFDDB86B907D9A3E6F202CAE8178E20B656EA1B5FD4B686D6D4F9A5F106341A0CFB85FBFF11C1F285CE0C9C7C33322B28103406469689CA2AF8CF68D1F6226C08EA72F6813BD8D8A8B3FEED6D1CEE51CE8807781A4CA7125B8857A520FD126B663081642EA95E8CA9D76D1D2052CB60AB54C6CB9DA1C0AC094A46E1ECABD8AC28B4C93E76DFDA534975F36529BCB1B1827AEEC5F55C82CA9645AD172298FC2C1BFC38AA1066B2EE6AB32132058F34C5F17F66D0CE4863FE636EA2EF032C9B77696E37F1261C80E3AC7C7633393C68B29E809000A2C266A51FCF085B24DC4F55ED477A8878904D080E55471AB3CFA53E5730EE3AE85E52EEC2235BDA6267A18684FC1309F11C5E60FFCCD739107A649F2D760A4496E289CE4BE12A5AF1C16F53B4F88388447E13EE2B789E31F1DE44572A01A187F0D8A1D9FB3F4EB2A0938CFEEED42618AB5FC161AF9A89E54C476D24E30D6046864A3BE2829BDE851DDC11CCD255D5D961F3DDD1B8887D7D5642D2C787E9343B494A691E9474E86BA7D14A1B77DEBD1F6871960F48C42691789E718D69A58C94503ADE097B7C35AD8A44B1ECC51F60D4BC5FB08D5955C83B34FB8D90A2E2306791F7EE9FE820F204005746A79F6B34F33BB0D01AF35EDAB77BC3CAB56B3612F34574CCD94A9AFAFC6F12EFFD2B4BB5C3A540A052ABAFB5A58EB4A061BB575345A8546E1EB2E94AEA8569883A674206C6E3F370400D40E6A2582785FC2A62865FB96CC9F5588452D6210B0286F3C5B80DD613D8EBB934A53910F90AC21091B340243CBA168E42C481BC0E8A72414C9DE279581AFF1CE44BA05E7C04A8F0EBC9B1AC4539D2FBE519186173B0B8CAA9CFB39E75A9C3E172961CBD68D2ACAA37FD9FB238947CF34E220C06BCA10EABA0718B4EA77AC24E94985E7941A787108134F2AEB8596482671540E3C03A51F68F0CA1757E64F2F8159B285061C9BA19CF22B6D0C6E455D146824F3100B69E1914E4448200C330C05930D1F5457A34CE4EB08D0BCD5F1445455E259337E2E30920342009AF94E63B6ED85D399524EF3149C10F5C5A086F9FC26F2A9B273FC5915E73CF4A628BFD4513C7983024410EFF8A30C3380F2E50AAC7821F11DE55F9A3B2C81BD0F2CBBAD36E2B9C45FB28260CB8663A953C4E766918F1AFF791DBFA682E2786BF33F4CB170922E43200274A45D57E4F1FE8A2FC2ACD5591C903D4E0C8874700984F0B8D196568133E60BA5A3C4B1A4C688CF7184ED4E9C078E2B14A576842933F12B5779D606ECC64FCE28262AF818E70DD813CB32D9CFECAE0E4E62696E5C300459D1D71B3F39500097EA25A81FD840CB6BE80EB5AA72C8F04164221E77A12DDFCB980489E73D82D6F1C2024FD39AA14FF870E4CD3C8F2CEF2D1DB745AAC14E80B1DF21D7640872706511A39F300BDE1C215C3C4C040E00F579E852E3AD6D79A7CEDD8DF5C9F11FC0C459A3CDD0206EF21459767D98B558783F9880932FAB544B89BC844EABEE7C94308DE1334DCA8A2964812B76EFF74C44172B0665AD790CB8EF74A8A080E9C728BE030115CB39ACE40E44FEC1A95025C46937C7BB67013E4FD84B569EDEB29BF4028C7685C181229E5ADC18E72C2AF605043B42425CBB0A546E92B4F347115DDBF28BEDA859D46550B4F8531C873F960DA53B590AC354FAC61B124EA0880E4D728EA1DC61A7360DDB38EA0D08AC7A0063FF988065A66A709E03110812F8040B713AF9656D4C5B34460C2181772B1D7A57E188DD40ED6598E741B14B70B9EB14B6160DDF3C7B00A57B5B092405A4BC98C99EF51D22D06CAED110C7E8F09329793CCCC913CECA91E73EB605C08066D5E05FC5692B291BCB3B700C6310F2374DC515125E6B884D5167CD87F1A244C37CCF7BFB8F4BA23A516284B396EAB7E533E19F77FFF8C62684906637BB770B2001E786B2001CE07241A3A079319AAFB635120A7C029D3C70109B6DB7F9AB61E00DA79EE8EF06EF056F3628317E571C254F99078E09305854D6721E406926DA57994BB0FA93EB23D00D0EB1860FD33550DD43FC7F967850621691B313C799B1C196CDF53D5A65EF497133F589C3C96322843EE1404EFDF86609B764B9DD34064F6D2EECAE7697E75B004BD11079577D614A89B16E1AD58E010672BF8C2BC227AD9F1FC3F2B05243F0DB236F08804A42F63F663404772F85FC695591E69AA6019CD6B31024CC1C4264391E0C9D08EC4E896B59F6FA181FF6F477911E4C10EC6353C7131744E09842B0B9401DD2C5B89655339303A79C425C8C13547B9A29441119F682801DD11FA26AFDA02858A6F6421472C3200894060C9D671C4B7034A149E8C873E79B0800CE347B83E93EB5A42CF433488B16416819CBEBA38919B80A91968FCD32A00859D50D96F8F63651444BF0600E368C6B1870BAFFC845D8E4E59729A60995FF74805CE47D7A1EA847C174C1C130F20B18290C9C4CE8764D66DABF4FFA83D2F979ED313474B5934D22125E847147F65A76D106CE89225AD2975582CBC4C1DB1108EC59964CE839560FB924D7722D5F177AD6C37A3F116520127C88905F2994F70DB17A5EFF068F45CC3362F467191FFE4A42129A6404FDF61C73278DF985388FA20265B01EC5890405CFE6AED7748FB68226FB6247B3E989028DA71690B1F7211EAD7C1A5B5EE2915121D145CF2B3F2132D49547FAF752A157434C3DB8F69386B55F4354743C093EB7B996C4EC3B4E046158D7C937321178864499FFF8C890C286239E51F480DA10F0DD0F38CA84D4DFBCE2394F981AF92C93ECF3AAEE468E0E4D3A0EA1A33DF4EB95112A9482FF323204A9C230D9DAE43E18A6D0F290FC174E93EBCA3D3DE2CDC14DE43001106CC04783BEF86A45BD596C7138686E4C09233B78E19F7193F3135F49BC89E43386F2C1DB34215FB6918C9DDA989A760101BBD2E010DFBD4AC20FE04F676F46CF4255236F52108812D97D3CC64AC48B4DEB7BE327F61AAA4D52C6AC2BCCFB2ED8A894F7523C7F2F84887D0A310C9A57824831B6E09A31037F093081E97A4401FA23221B415F7B2853832A861AC5692B2921BD4A829D8C46176CB0D9AFA4E2A613B6ABEF0C421562D74AB6B7EC530C4CAF36F4AC70D4A6BC48C998D830E6AA167AA9E92A8975968FADEE4A8431556FEFA5CD8216005A225605B24DC095568166BB728401AD5418C062F81F83B6EEA6AD9DA335DC07B84CAA1C75525108640A1CF2A27DE5DCACC1741E7A627AD640A18B8783CE7E371C23968A8205AC7EFC4A6844DFED2A8FE126266A41E67494887EABAF60C3FC744C2FCFE09239FCFE91A6C034CC1FA5240858BB0174736387BE896EDFC6606FA39ADA03E886E5F660F9BA3688C07ECEB02D0944910DEB1B5ED6C65699E8DF4C973F2E0131D28AC2B9DCA39D10A7DB6050FC4C2111C41EF1967382EEFAE507E88553E2DB6AB50358600AD0FE8A0A4B00C81117CEFA33F93AC918DB4F758BD8B59F05265CE533B9FC67316C739922DB96783745F531A663DF80C703F5CE7C34AD57165583E219693291058AFD0C1EBC180E89F7A47E5B15CE41E1A70D6D43D2F6B60DD7605EB50C7E3AFF67CA9BD348D9790291F5C4D30D0DA8DE07FE303C576A5B39A39E55C615F83348DACA47264CDF4136037569D2CF0484E52C461AC8D22E7F91250B9B306ACE71DED5637263955538D04AC316123807C9286ABBD14538223CF2A446ED8110615280F015EB0330C3E9AA77DD7C11938F434CE346943E06913AE4CE6CE112B4F6DD95124F4233BD27E897C05C441A6F36DB3B565212FC1A8E9771C968E826CC1B9E6AA4AF5F3ACCBF6518B38FF4026FD247158505CC109F60CC4D4DFEDB0E9B885309B5DB3F48CA00128EEB4CCBB8AFA45758CAE49122DC109DFB171F4A85D40460BCFCBA5A9487196420DC858EDA59E30C52F2883A5DA570099CF4C225DA6500D869AE955BE49FA22C62AE7C710974F1236F59AC43222114F9AD6E6E9945309FEC819D23E146D8F643C61BA50291C6701920DE883970502641CED2F342D13E059A5061F1DD117961AA5ADBB190AD4BD5781D2AF9992CF3A09E52FD2E867FB180665C0A059637328B9FE2F4560216034104E1BB00E5C82C46B274291C0EA50320214BC228398C27AD8F8C82D746083A04F934E4DC2C0B7070710803C1F9B9084CA91ACDF2DC0A34B53EF39143E88835A104487CEAF380A7C00DACE48A3728D2048438FD3EC255D68A566DA4180490A3CCD5788603CDD559606EC5EFB8861CC1CDC5BA49301BD5163498B37DA0CFD8A8E585380F7237A319AD7F1EA1C32BD49C561451A17EDFA47485BE0BE077725C9560A0C262A8B534A9716483123BA790CDD9094BEFCEAEB7C093E571198796C4A21517523B2D091E2FCF42AD241ACA64D980CA44FDA91098E0F688E3A081706D43D460CE9403236AED42BB55DF1D5672DE8871CCE42464920E98D316F7F4FDBF930A1F0D828127B1931965B499257A8E8533C6CA30CCF312EC468E0A70004A45B073A163A3B943159552D6DEC090C822BC8093A467C7831818D6FB5CACA20347AF117E79F555348EAA278F1493AD684C973AE1B5B1DD1BEB92A771890E43E078F08300BE9B3394B18B1CAA976A034830C43679C3D831C416DF4C3334A9EA6253630131451AC729609BB45AF9AD6FF27E570EA438E33787F29040AFD12A7B1C770FC39A9274B3605419B4A641FB61AD1442B223C8A7C58980BAF33864C87B1C41E25200D880B4D26CAB5BCB3B5460A6940A222986672CF947B473CAF496386F510EBC49532C8993F2856DF67FF8205E3C0F9104D997906D24CEC47FF9339760C4AD1191D192F145188E4683F8B41C7E7A43BB118B44384622F2281C6C0CF74B849A673357A768D524C0D5C57681230B8B681FEB2C027DCCDA83ED450E7B6726436BE85BB645B16FD8DE7D67FE20ECC380AC5459F878AA0E8D418527882EA23F6431941367CF8BA6F9E763CFFDD811BFC4F7C5D4FE2CC8F7E31A172B1503F0B1DFC4271CEB480983E6B1E42F36539D1E6AFCF646890AC4924063565EAA55ACFFD9ED231A68BC0CC1E198C05C4B3A98E8612DA8740E09F92D488EC9201D4EE6DB698437A8AD8D393645AC72B20270FCE1C139984E4D790387BC7AB8F8960ACD6A6A79D07102DEE1488124CB28B382AC997FCACE34785E95768BA9273D7B8F4A1BFD64B76049464656D7C3467CA9441F99AF5E8DAA0B0DD66F2C8EB2BE46FDA405EC84C9B013F7063C38920083B1B54A183A313B3DD1AFB62DA777F180EDED072209104C0238C12FCB0A0FCB45CC7179FCC7B9516FC787BD0A8B235BA225C7EB8DF92BD14D2E1880AFD08C68FA9E43462F2E8465E8299C25F0ED4C9457E0C42BEA4F13465A0922A8C3BF445CA8DEBC442F6FF8142AC2D2F192152F97B0E79C44A4A42B57C23152693A31EF631286FAA44E51729A28D63784CC425DD30B5FA97781D575FA125C16DB0CAAC298C6E9DC57F38DF5BE47C26102D326A64CAB4C0932F9B909A3C3EF473CBEA1FAEA6BF97DC7EC0F1AAB05F855E8DC4CADD0B9473416BA30D0D52CBA5D45778DEC65172CC0782FA328FE58007529EAA28AD270F69117C062CE48636DD7F8B56012E626033FC0928BD4E016E5905D22E34ED7B0FB5622B1B0889F1033F3939A18B15F79FFF981A8AC831AB36C70685A641F759B6663B1F1E7A25119B6BFA2696B5109D0F82E2C685F51148CF94D6C993BE07872BC40E0538C74F7C0DC9C32CF6F9D887DBFF48168DF7EEC618484894850734BE482D7AE8AC92E5989943489523E1AAF9A8055D1509CDB3409EC497A472E51DCC8417818768A8CB2C2AB08334FB2BD14A8AB091BA153AFE1EDD671C14ADC068A31980D1D66097C2AFF335C68097EAF985892CBB52B9CE86C9A09A0D839F2904BD9FECCB2C7AF44041634B014CE0013520E84E63EEDA9BACD8D562031B9696C285CB5E9B644263F1A0F5553B0BEBAF708577ED5ADCA02C235B1E1D1168249D14AB0C9744DEAF03A4E15880837C13BB9D5344EC101434835A21AB034529FC496A4838FCCDE9D48501DBD9A3BC1868B80207EDEE6B67A883324A243B9314480C1F5F9096A5B147B87C4672914C2FF585DE510A194F730B33308BDB063DCD924FD4207083FF23C19739332632F899E0818F68960712EC0B41DDD220706F9A16B0757E6B6C759BCF54A9F42A3C212AEB60FA3881E6D26CCD92912F2F877C5FACE9640A6EF1DF1BB453CA111965B75E479CEA035D963EC59062B871834F8448593AA5B0951BFC927B495CFA796F11079C0047F3DE6823DA6E2C01414E0B55C3E949734678E6116A91F0C9B181251D18B02F515D07AAF37709A6308AEC77829D0411020C3BC10278499E947F8EC8EEA659CF1A5618208B01C2DE8ACF88C6AE4F45669FF6A541CE73132D8F87DA60A4C99AC33189CB9532E77A0B0027D11F2C3D709F972FB82E552F1C70B1CDD683BE63746DBDE3E82E27A91940FA80528CFE358741896361D783D92B110936397085CE25198E4C0EDDAEF80DD80FBC2B4D45F77C763BEFADB2FDD37E6258C2ADDB563D716F8CAE3D7352C863AC96AF178642E3C0115FC515D8397212BC4ACA838A01ED3448AB7524A611937437A73863B8407DE0C925D291F227C3ECD98169FE7A7A00A89A8477B083E0BA548B5D81AF84EF7A0F58E571C305D26A0D1A9B175AA9A0BC00C6DEA795A42E3046BDF4B26C89B3BA4402E983B6FACE5B0067460EA8E12494EAE0A34C5642902695815AC8DA4A5DD7D4C023CE3F32231B6B743CA9DFFB34E5814B99350A5028C0E012754FFA7F8EC955201091C41268E88860A26075138CFB8398F540746ECB74FB8F45233A7BA9398B36B372C7B5E4C60D5006B21A0BEF8D443C66CE4398F31A2150FA53ACD8B91E2A5B1A71C3108B849F4EBA1B2018DCCB7A041CAD0492A5B2B7D31509C411FA6D424CC248B80412423EDB3BE697C016D42C2500CAC819A4003B2E588020EF0EF8E8F7B0A432060881C81055FA2F573F1DD0A3030327869F704FC1795C6B9B4F9038AAF1771A0F83962D6BF12340B8DB0E49B8086A6051BB1EE112C0534229043ABCA1262877BC858AD98936D71D1004EB193244201DB0D014947141D0C42A1696283B02E5D23722696B1D092220874543405356BAC809B34E50EF1614202F48A3F78A82D8A885792CAA4B77F9CF09BF3715D0A25C1C604CC5266BBB1405EFDBEB4F21D80E9F2716B652763A2E87E854E174BEBC2D52C610B8B707D8419E01211440B86A9F2625E6C0FEB16D2ECF45843867132BDA4C3433F8BD6269FAAEFD21E5B101472700FA6DBB5D28E9B0F7936BDD220C036F04159DD3888C4BC8F017FF020183B8DB45822F3303E3DE3188DC6351D6BAF425AD15D47CC3095ACC6B747015A821AAF5DD10407DEB3337CCB403C0E94133994E989F785A642CB90794E91B8F36BC520FD74BC727582649AA698BE7F640982F17FE1C64CF07E51D1E307B16C8765D9596DBEFE0A1E3EC39234BADFA6D58214A91FA01900DE41BA8E22BB49456E61700D0A38D97C4E7C4A23A574866D481A9C606FC83A2D117932B73B2B541A1527DB1DDCD52B6ABEB81A8BC52C4D9CAC3081804264CCF66F775E0258510DC4045CB802CC8A111FD18EBDC79F661B929E33E347AD48562FA5579BB35D694C759EE500645842718DC91A2B9FEB4D72F4624BDB4D5EB5E02529F3E03F51917503FE008B585D03DB4BC1BF29F6590DC37DA1F445A282E44A3C3836708E3014D5516852E1276A2815895639D31DC3F95C22969FCCD643182F83EA2607BDA84B2E84252B7004839C1C2B5AF1803319A2CF87458726E748AB78B35D12C15FBC0BC84ECF20EB85098508307210AF120B4E1B4ECA176FC605FC13FDD73486D00A9FC6996DCDE6139C10DDF4DC868E79215D588B35100D03044AF5838538B093A6E78B5070EFEA4038397708AEB21DE1B81872947196E24B9280B4387D1F03DE22E21309C58C3E108A4E5E4D408DE170494304325388721724E8C5251C233869F80F6E7A9692720DF0C3C5C8B8C21D5CD177E9F47F722D96819C98785B1CAFB87EA6B0269F6B3641209523E643CD045F31A980DD2323BADA84E14FE9289F48617B95D0ACBAE4C6083EC9F5C092013D32658DD482039ACE8CBE64557C9BC742D29E0531F6FAC091738772836DB264852FDF89788513F2F5D6378D5CFAA62BA68F7A119981BE1C014886C0644DD2F28E469E3395DCAE1D4563122A4D2B2B0ED14A35C66873970A1DEEEFECA97BE750C08C514D05520DD7C89F5C588068FBF21229A6F86ADBC0AC79272E102C4055EEF9590CA0FC907D44C2466F3E075DED383772A1E894C244E2AEAED44766B9C910EBB858B169C8C8B70621156D0EC12833566E0C8E7441FA68918D6C91A0471A0C8D6E99299DDC6CE636ED2A0698898AC65DE14C04D505EE658FCCB3AD4346B1CEEED208940EF0EE2F3A1719BD6F0C8DF1F91756C47FA5843CA3351DD12509670605CE2394EDC2A19071C89CE827991DDEC83631422487B3AFA34876F1C0424B34753CEC2E82B76D02CF73828014DA48885CD6098707728C75B1C3FE23EB47633CB724C098D937176C07FBAF6C4909945BDDD62D068D70D54B2DE5C2474923042C7B4C32C8FC63A2BA2D41453154"), Hex.decode("CA09DF7F89B9B339C8D506B4E0E73FF145F5F87FE26354532E7EED21C6FE3355A737BFA0264CF661B9CE14128695D598254BAF3EC5C44273B375B70FE7E6457358C2459C1C19D589DFCD9895498E28A94D7BCEB9EB4C1CD113012AA3719165B7CD0507DCEB55F284CE537E82F60C0F83834833D02B6D825DA2AF1BCC6007B13F4F3666F02248A83843DE6ABAC5CC4A68608C261AE7CF5D8809956C9EA249AB005D0571D7399F7E51E5C730A15AB556E87C08067EC122998E0B4EA0C7D51B681FC82A8640333401C37179007B3DF4648D221CF2601F0B4223619F7474682A2607AF01FC5E6D40BD78E10982EF070C16706093A2B408583277D42D76DCE0D2607695A48254E8533E48DDBEFA39AA25060FF3BDB9B234964DC82B662DEA346505375A42A1405A78F294D4402ECA92C725F243E2B9D0C2974C05D45BCA30B97072CED73B30837F13EC7FC0F80000D26DD73D5E90F707C60157A7CBEC0749DA50CA136D517D1445FCFE770711F172854881BA302EC27A960F2CAB2BDE5B0631D2CA040CCB2850E85A868E0217384A46F266141EB354413F08D1EECD054632846123B83C9C15A03D83D9446A6127273F943538D60B8D7E98C9A9703CA87605F3BAA03D8BE3049F05394059DE0141541F3F08C298D05B0C3030CC6984077D5CDF9AE9D42DB1FB342CBEBC411A53A51438984B3B4CE07B272FB6D2FF5680E0BB6325CB137A3F26A6C86E5715B3C3E41A84DE438A94754DB0A19A1D18CF587680FFA2C435454C148040DE321E13E31929B348810F95852E0B6855F8E31E227C71C1062C8C61EC404A1F4F2CF73EBF86E92050368794E2388F5C58D171251BE631CCE534FF0437675B630A2473AA239F1ECA4E641369E9C67382D41A5111BDE590202A2336CA8FDB8132767C0D59E3081586C665802899275448463FF284AC49205500ADE4E93F7040725399509BB692C8EA04A092311030594C51730517D3C96D79EF3014AC404595D70E764F83D5B871C8678C0819239D3D8D63B1E29515E4AC731CCD93421EE6FC3AE78F520F40DBEB6B74ABFBD4355923B9F66E2ECDC39832420D7C575B89C82F3679EED6460D6249AB4CE037E8C835F18406ACF8CAB6DA80333B1B8201E0AAD1B3C8DFD828D1CF50B60FA5672F7091ABA26A21996B27A0ADB819F1B03785C83D004F8E000D8FA81956A378DBBC03154F766D536B1EFE75BB1C3BE660209E5D1E4ED205DE0B856138B57451DFEB1592ABC9625D8352738D50A1D1BA0ECE4CB4854CC187C7F35467879294D66B95F022C1ACCE296CF76D780770EC9CEEC099111BEC60E6BCB031D45318156BD59AD2F05A0264282C167326E07ABBD40D924D8A3C314D586B5F4423C081EA3EB8F9D4F85240106C55074BF1B38D335FDAC2BBEDFC6F61F02DD6F5472DDAB4CB5F84C09E804C35E35AC9637428D7BBFC889D32E78E2E0174F792D66C2940FFF090108D26E2FAAE166A92538B761C1C8FBDF4882102C2F607ADF275C20943D16527BD748A83B04BB8FD5018C5C5DF0A985644CEEBE3C200741EE8A39BD6247C662D7C1A24B500F08BF11E8E460203BCA47406D48D82502A41DEF1DC7FC9295E193FAE94057244B0F786F7A24E454699432CFE2F3F7472C63FC21131C7B9F6DF8BCE2D930CE65FCA9014A732D7BDED658D1A6CC154D7D995A9B1CBCA84DF26A2B65B080E462D1799E85575413D61A38C260E25B69E9C3A039BEE3395FC1DAE10B2C158CDDF4319C347074D89B27848AA6B05A13ACD6C51187313FDA50882E6B83B30CFB2319B4F3B3F5AC3A95B0E8A5C9B85E60E79AB7483DAA8B4769C14B75E28923839BC9A50AC8B2D424624A9F3A1B531F0921330B7B9D97C839AD64B1D2F9DA93B3A8E1A2C9A72F0D647C8D2E5004164B18130C39F54678E73E22F716CFA467918E440A159A8514A40ED2F0DA6873B9952F5D25414D2B2F4C21FFF321EEC20AB81E24B106AC33C648E96AFF7F293B7A8D676E21CFFF481EEF83F61674398AA01669004B5F7F86E54A7AFA6301392BABACECB2C18E0D14039514F982FFA26E024B5927E6DDCCCED9FEDB07A3FABA8C6445D0D63099832B133089E79388D62EF391D6CC089A742662A5898D7314C901D71004ED6C6D85B4E4561AFB8AC3CA25B1179474CA69D0E7AD185D9F1A3D8D066F8399BECBA4D430906446C3622F920826437E642CEFD375783C7D5A2A31326C0634F2A88422C456065D8BB515D818FF1ECD30FFD6A377103E933C086BAA9A12C068302E281739709008E2CFE66186A0471501082D1417EED5D233301683D2939B3D4C8A92FD5C4C3B28182FED67CFDA6A0D61269E2E123457BF8E436F57224C7F8BF7B63718DAA975C0681DB31508AC84634FFD75E55ECF9EBF27A59BD3D06BB909077D16C1A8C82F6340301FBECA553183CD09D72C320B97F1EF43C96F7DCFE0076DDAFC532C6057EE06C16120AC0145CD02DFD64106AC7AFF4F555E0ECC0CB244A21919E8EC955C18D320BE0B336FCA69D3FCFEBE59CD11D23D048102CB13879DF6C1A325B97534C5C4720411D490458ED1C5C04342E25CA0C94C2ADD754F672F80FD2E3EE424F7891AB751F27259EA65889B92FFA7C04B24D4076166ABA495F969944E6C41A3815AFA860B1465BAAF4A0FB2E139D366980B47E9F72D723A00D4D26AF88248DA3494B939D70352131D3D994E2E988E25CFA3DA83A53CA9B4215CF894AE103770BD04A4C75CBB423829D82E959D29BF84E83C5280DA95E27B5EBB45767C5CBE425A6B431FBD9EE12D694AC45268E81C26716255162288A20622ECFAE30724329A136D714DEF2B8AF842831F3472BD071414EAF694A8524369E97FAE312ACA6498DF1834CE9D8F96D1265AA59EA68124C32F75CB8C1259DED4D7FD0F5212DA0B2828B74FFF17D4FE475D81A9BF87C5F539A934025C6E07D853C2DC58A4AF75461D94BB5C32FD4A98D37F1DC90418F3F1EA9220CF6807B505A503F628A8969751161254226A93F20E1D042748E873296C1492D1ED5FC9AB7A8EECAFF71F114A49C8C192971AAFDE226C93EA168065FB0F03703D3B24247DCC7293AD97C1710CCAEA9FBA6FAC896A8ED9449AA16A76FB53B20C0C80EBC53F109F6E37582ACB14C916068B288052CA8389F456401D84FB3117552969A693669526E62D48ADCF24B2BC60381BDC3B138B25E2832409800FF7AD3009DECA801F408C8B251EC39BB4DCE6179119563F6FB451E43F93A83DA917C81E5C77809243F212865AE369996D62660F3AB34BCEE7F6FD8D1A23B0A8318211D16F4AEBD258B70F3C7E2D8DC69F2B52CCA0B7EE70B10B6C2D910A62BDB02160582C0641043A84F9AE710DB30E718245111CE2DCB00B82B1DA1D29F5D059BCF062DBFCB5DA4674E14947E8138B127D2DD201C4859035E4E92FE7B7717E971E3801827B7436E7FBC790791A2010A8232BA0BF3129B9C54BE3DE5DE4DA1D4CF6347D8287671C65E91E5BD86C5E69AC636C25511F6226DB92A5B6E92272E3342D3B605E2BAEA289121B14B19A4DF74D4941B24DBA840330BFFEF87F906BEC30A48AE50C9721CABDD5F12ADAA1D33A0391D7FE225BD111FC4A85447248C6033D9B531CB387FA3B29C70A4314F0C88DFD8070529BA8A52033B4379ADA6E37263D6301CADD83A9900ED804A628D98772577DCD8D8BFCFA64814983F96A90F9536E68BC05869EF1A1F99FC90EAE48E55066AEEEF9CB156142007037BADEF631DF535340188F5F2E7A415EB79889E6283256F4223FCC58D484D75F987EE3BF546C6070053C66D779C0F210925E30A152E80B19ED8FBBFDDE55801CA44E435481E27C5E04F27C57DC147E026290510BC09331B0EDFFFDBCD2A66AD13898E223818F4806196DA24F9B91AFC4E9B571581E0002DD6BF91331F3A640952ECE5DD244FE1474838595D8308B22DB80D560F4E37D3954859C074456446851D91E9E99E53FE2C3D9D0BF95A36C882092C40060AD0884D17295F7D454397B515FEBAB538C3BDA2BC4C1F93A0CCAF2C77E400347A6A31A1DBCEB24F008318957CE9D399E4ADD2A5844D2FCCDEA92D5CD3789F624AB34F2A387D600BACD5443AA18DEBF5096228F6F249C6632676ABD554875E7C3E8DCCC89F00B16343E164BF643825FC40DCBFD909A188B93985951D3D281441202D3D75C27CBAB4D20A5CCA82403FD5A4AD92ECE876177CD2C7B140A479BD55E9066BE4BF1AC453AD389C3311DC40311C590241B6B612BCEDF7987B8CFD11D65D3F249C9D7C638445C6092243099681F502303AA7766C7D1314F575BDDB3F388AEBDE83A6C1514F4C881C3698AC89BDA978BB9B04637BE90F1867F7126CF5EB458F681016556BC551514BC8B90ECF78DCB2914BBC40A925D2401A07AA6D1AE6085D710106B6433A1CD1A34C4CB1A752CBA1536E5C30279E7775DCA27ABC86EF7A26067C801631AD6BFD286AECCFBAB22532F51EF33ACA53FF8F80AB55CE2C0502F798FE4E727BCCD8B99364DA5417E24836F97B98EF05878C9CAD1CCB42A1443023173FD11EA32C5570F031686AC51C0FB9259336892E863EFBD7AE5691C69046CA1A6B11D7D16D528A89864258131F85F0C0A9F51F07DC3A8FC3ADE3B817D2DB1769745E4E59FD5AA1DC7F1C6422C7A51C23846DD21429AC07DB6769923B4650F9DCD06D25DC524912DA42567BFCFB1A9A0EA98BA0526C6BE7FCBC86CED7CBCAC9AB6CFC261675187DE67B3950861F58E47E0742D8F5CF35CCEE41622D0584F89F0C829F8191A2D33D68A64F518E2F50D44BD616D721DEC74E6597AAB4002FC87DBD9679014F9A887715CB2515559FD80946B41A4D27490AB25560E4A1FADA6834CF2D543EB9DE5378D07842BB8450104D39342E21B522E3A0FAAFC9D0771AC27B1706AF93B13C8AEBCC8D46EF5392760967A3B31E4900D5A4CB080B793558A56C1F1C4CDC41FEB54E85FA8444C7DCD7C364D8411EA0ABBCAEC8FC06EA482B59987226B025B6B455C31A1FDA8F6BCC1F00E5D2E9B605B330EA0A407B58AC110DCF1E7476A9DF332752EC63D50DAC7B4380D1ED52F080B060F79F2F145D318E9A09BA567B9863C23D943D58B3EE7B2A07D80402D11F1FB0B4CFEA9170AB3430AF016E08DA6A1AE4DE84E0BEF97C43FA2FDAA6EF9336E8F7D18D7DD0B953D832BE2C4FC05498BD8EFF0BDA01B065A0D0CE54E6C580031A79F19C1536BB261E43028AD8A0A4D4AADA0D5E7C498960289F462672AE9AE4C2A11973D0DC5A66F628BCCBC580D0D507874D01A68BC2720F82847FAD98E5BC13444663B246F5C269146F64A0F4FD6FC1F4AB9105C19A92566F8F341410C7788164A97D8ABF531C2860DB9004950BE0A92EE7E5D7B88D20183479419B5B2520AEE1EDDC951A4E6D4290BA2387A003609E5C826C470EE476F9D0109826F16415B0B746D87D093A006022A6F38E4EFC21548DFE5030C7BB45248067F5F9904A8FB874725D5BB4137D2E427C170845CC2C8DB4F20FB0A6BBF368AA9C4F206FEC9A4A567E65060DC133DCEE08DFC67656EB917BBBF1CFDBD1F315D71A75E8754A4AA9AB6D35BC618CFFB2DF03E796CC00D687CA807BB974CB1B298F1D31ED2FFE0D273460BF10BC99FDFF602C619E6A42EC55817C0CE483598EBFD917DD06451E96466DFA089D67EE1157F2C9D7C409EE5B50C048DE8BD31F0F297774B226661645719A8B8045EB2912820780A49A7C5EC24CAB39A218FF6C74576C5810B1788417CA180552BDDD82F2ADC38CF91192E2F268EF78827DA8A5754A6AC6D2F860EAA20A863E54694DB6C27D2DB37CAB532319F64804A5CA5C311A26BE6699830AE8B4B2DD67980CE9B51D828F6B1887B81681014C9874C50E043F2E0A8D7D36B914A6EAC8E0DD581384CD66E87A43760C838896AF808C33122356AB7C0680249EAD3A291C90883EDD4BA914DEE43DF13E1A70844C08326882A818CA5BD068C58AC34BA919D25EDCE6211866772FCEC1B985D651BD472DDAE6B03A6A9E0BA00DDB3FA2D62B07B82E05A3A7A7933CF0FB8EC22399B3EC77849B8D53BD7781971352EC1359951E8D4FD06244BBC2C6EA645BC37C491DE1C2EFCB1A88D903960EFA4C461CA21B4059164012537260B060652BD9ACC36D0223D575866530A9FD7DE11DCF18FE44BB84C0C6205D77196F349DC914736AB83CB87506989F83A627F3DE150523C1BEEFD9528489017C023B6738516692CE73CB32E13AA3B902DD2D0A7908760D12D6B170E3D04048ADCD8B8DF55FD18082F911F87E93113E1A66611F176AA53DC6EA272D6B76115AA6E210B9F1C23EB8E06EE521A3806917D584ED58CAA992C340660725EABA1DEA69C5AB8F05395B7B0BA8F414BC63E6FD42AF6AAF2A5CBC0AB15D57E7211E02A8344056E53A665F20EB760237A5CFF3547F84F4098936C4C05BAAAA6B6D7D64F07F9A047CE77E5B2E6234ACD3279386C6FDD5233141F8A799D62DA14D03869C6DCEF12883DA4A846FA286C8D5B09522619FA037D88EC559101C42A3B4379142EA7520513A51ACC3EB818C00450CB7F485E4FF855A9224FA37A89177C09DE1246FE245C366F4A96513C2E8A1CD77C4DE2F6422FE7204C4E501EF7273A1192AD5B2E6D974C472562BE666BF1261CB15CE9F9E24709AEB55A96FD838FD041F1274554046222BAE29F29666D3FEB06145BD3481A1B82B65D3FEF880491F39C21EBB2C03C70648ABFA376749E9FB3BC3997CF4A7F94932F37D35BEE18597C85A223A45D1A3D3A426D0D7060CC412483495EC11A0535DF8D8A6B92DAE50FB18E37848364E6D97E2D4B7CC63BBF3CFA62AB2E32321016A48B8193E82A2D3984ED573CADFFB90CB8357D85F6220E99EB60063A84B3E3841A34FF102075F90672A3AB1055D016DB70557AD7FD0D02DAC7D6794625010E685FA19F2334AF6F660473D066E280359DD5D2AC4080CE28D228066BA663A8040FDF8E72AD16ACB7661C993813E5AA016131BFAAA80437E04167E6759F93C453D64A0EF4DDA10057FB60D522F059A994E679027037BEC7F60DB81281007CFA16176569A663413265B7C308E04192E9D87E48D8C15259BC288672969A94AA2954E2D210AAE043E02C030A11E96C9D0983B2ECF3BEDECE727F834615731C26D5F187AE0423F5B34408288876254129112778DC998B713490346C955AC0E0CC86D52423A8C552E815902E686E28FA466190B2FE042A23835A45B3045CF300AB66DF4A0615C93164EBA72FF460EBC6A728A7B304E6719C719FB18410D3DEDAA751C02FF06FE18E8C26485580ABE81784D3C73CE72F734ED688EDAE3A06D8529FCF7900C029FB0AE926A601FF3739FE6EC7C5891D32F91DBF47985004A766B3C93611DCCC47D3E6F435EE0D21BBC0281D4D273172B3952617E24AC292037D6047982A6BDD0BCED4653CF5E07F7662A250107AD9EDFF0396913048030F87ECAF23310B9A83E01A46EC9CDD028CAEBB3DA078553C3FB3C61F7D41ADB0AA0B890447A0C07BF9FEB04DBC5C7162A036007D5C8E3199B21AC6498DDCB5760292783B0C8FF3D88431ADF9ADB23209197B5A1460F7BE09826EF91D1B6A6F011BF52CAAEF9AD34488C0594A3A4EB34CF41527EF56DCA2EAB729518984A1E30FA5296759F08EC6DEBDBF5D1ADC02C6DAF13B2D69020ABE3419DFB38FA3A9A8F7782A30F980B09BB1242DF82413B2FFF6640126088E213C9F8C3C2A6EC4DB005907AB48D1DF60382C61A14683A5030D2F81A89CBA572EECCCF26976EF2D61BED698F127392E71C5CA93C0C61217E92ED22FB1870E425100E46EB34B06819DFA694AECBD922A0187853C8BF33BC07EC68CCE254D4F6B5918A0297F6DF27C62313B4C67878C91A0410AA1B6142C0BB263AF02FF80BF4154BF14CC29C5ADF9BEC2A205031148B634B8D65E688D3C4431529C03B91784C7EAD5ECF020842A47F327142AACBB901586BC571BA17F2BDEE218C53A9E58333033182A667273AEC10D1CF900858DCC504694459D0840528C2F52FF2D0C080DE481B0B169696E3B36866E50CDDEB398B63989382DC2734E032179F2084FEDDE0CDAF2837E8C11444C1470DF1E054CDA96DE7971AFFB6A1E8B34D84F4408BDC7E932F5A2C63214891E0C16CBF8C20C145D18371B1DC6A339FD19116EB884AEDA668D8EE2CB85F68CD0B9488E24C06C75F53A6D9BFDBC71C8BB17E1202EEB10B7D3AA7F9AAF31457DD4981426D21E91DD76CEB24EA982E68A12F77850297B0592C9287996834958C2560BAEA0F3BB8E76BBB1224FDED7AF1D4A6ED880BD95F128F32BD392B3FD343D58E8C5BCC9B1424BBDBEAE26A2120AD759136DA7F28381B9F84CC902C4BA639294255F6344483CE34B37FDEEC50544A1D26184E76D05A6B0FCE9606870FD32E0E2619C7D6E280DED92194A60D5E7CCD7487E0602BABB62FE3A4EF15846B7683E7F0D262AC233C735ED78DE58194D288435700A78661B753ED26054DF4FEEE0923EEE786E5F6FA1A6666697D33436A4E9A3054CC82ACF0BD2FCF903464A2389C87A67D669538696E658D1CE6DDCA9AEE1131AFCA2DB21376D43E382A4B9810C454E1B613ECB972B9BD60FF35AE698B653864DFBE219E9205FFABA6700B514F41936440D926B3D1DD2AD696E8E86107AF6663A43FB4F199E5F271ED3F6759C54FAF4843B9EA3F0814C7301C77995A43DE6793E15027DF52FD7EAF2AF3811667430E52451F56DDF85BF290F8811D586A3184836217DFFBD78CE485E72D209AE267BB4952DF8D301A55A6BB1368FC522A398D35B59FECCA5F89C0635D83E6730990983D47AC6AB7E7DA85F57599C3C69F85DFF280BCCB38C2F515AA60957ECBB2F2010958C234ABE4CC7D755A82B017BDC2BAC5FCAFA7C9D392CD32769641DBC5D8C8CC3E376C66991A0CCB4B440015B3084665163C4876F488224AB6E8E0B2C215C541084A4A15CD774FD71F110121570E8C05A06064F8FDD1442BC841A78D6FD42B3D398EF2470231BBC717619E01144E51952F8F1EC605DF00F6D98D5F18C1D26E0271BD9D51CB27C09F99CB749286A46DA3DA77F3645D181DD9E24D3DCD01FC0BE2347E588A4042A49526BF89ECDB3B6A1DB48760C03B6362F42852FD49AA903840F557EA0FF6920C678F1AD06BD5C8E7BBC1343673FBA164218E09720F453E54273DD42BE2B20DE1870C7BB6312F833F76F2C5F2250414A5F3554AD28548F29C4273AE67830B361ED8ACAD7A4E13C7C832DA0958A9178BAFB4A40017CEB4E9697F8B0C055904D1DAFB539D48DE75A3D2BDA6B7E00510968CD9C5F4B290E25C79E46BB9562C5999B4762B053660FCE627FC6E92CB6288C529A72938EF591413371E263D8D71949F64C3001BC323F8F03F2834E34FE4CDD96E61305ED478CF278D3598E07960B6B749C34C22F5875FC0F5BB93EBB3A157EE54200C2360F08A03196FEFCD8471360384F32314E2F90CBBF887BD63C444A097A04F9679E373E9A6A0FE0FC730E471483485891261A1579A54B76BC8165E243E4CB7377DF346237CB311D6E1758F4BFA3FE6A0FF05D3B82BA44BDCD5E8C9E418DF41F81DC989967D329C3D53454D6A4D58D41B8AFA036DE038ED2D8E9132E9357740B33B918C8CF243DB3460092FB6E5E264A1924AC4F249B9A774493C4C3D2AC7B672B1E998540A2609D4049D8F5F63CDB1ACA366B7278CF90579926B3C55230C64246A65FDCBDEE57C8BDB34E36508999A9856B1CF748DFC999827D58558B77B322E0091E1A630C1C486997FF7B56E9CAA0621E569D11E0C3325CE6076F1A30F812A0692BACB338BA1F55B82FF39D7498BEAABA06AFEEC76FEDD62F97BA9027666DE9F49FAB64B54107B51988801325D9B51119E4AC32EFFBE13D1FE28ABFABA759A04781FE98E6881F29C994AA1BC5E9D0DD9EFEB18484D717176903A74D3EE1B27D54CB0E33CCD93887828523E41D115F7B51E7E0D5D43DC484F1BAE8111100C3F30E72968DFF0462780AAE01168088FB3EFCC30F329667489018AB35A9691FB76A6AACE8B02B872437127962AE12BFD8C82DBEF2DD89EFBA3DB2B63C2801C0EE6BD331D21105C1431FB867FCDC51C02DB248861348C543012A8878A6056C8D047270461D51EA4D0A85FEDF1AAA416029B523FABD44E3E25D9F555A1DDBD2C030F3527F758A0EAC9F69428D3F74BE6D8427AF8B0C407E0E7818764AB42004829C8F102AC9822F7766AE94A40A8F8F74F0C0FC1D601702319530BA0AA86326A6C555845D1C85DC723A07FDFD4C6F85C2F5C4D912CDEA186D12EE418839CE1F9456A42C603605D42BDD9C41DC40DD0720BF99AA3FB0E2A807EA3A406894C81570A323DD8DC67F445A53FE0DCB0C0835293EDBD4F850116D2B3896D1ED631455D9C9C5255BFB20453A2DF76E9EE3460A09C24D08CAAC998A080677D755283BCF4E4EBED728A81A6713B1A988164586DA4A96E666F5ED8AB824D7931C4E5EC3B6AF1D623A3ADDA1EAB6E7ACE419CD63E81CD93E81EA4D2A64D25A59DA51999707157A512B4033F52C89ABA4DEAC79217F725F8D881E3ECA42FD0A7C08520E273C9E2EE15D3E704AE06312116D3668E85E41E94DAD2FB0F4A506FD40431251AB643E4A866B361CC42963DFA57E9249904EFCFB430F30346A02EC19997F96A3FE2AEB5574C9D065B68804109F32ED27CC6DDFA24D86583C58494960A78AA820D143542694708F53973150FFA3C3EB73B04592CBE8EC365EFF2EBB491298E16E11125D37B6072D67C21E87EBC8B410943AEB7D8E23C17E2C45C05F46AA6F852759A78B07CD5463CB30395A5B407765D9CD99B00D40176D050D0AB736D663BFDA7110520639D1536022A0F99C9A4995DE1D6AAFC7F8C33423ADE5376E95744DBC797742F7F24258FB3AC93249B5BB7112A23E2D1F8316803D2FA940C5B5D3D66D13819370DE2FDC4A90494EB5CCC69A1421D0DF68424A0CCB0E53A7BD18682556A540436A8B43B0213F27958CCB5D5A474CBA693567663521140F3A4BBF5848CB4D77932A1050299404ADFA629566CDB21F91358CD918AA65109C971335E041A0661BEB972AC835A451A66B6AF503148DB5587540D4137A770B16412C9709AA6500364A38A24CB9C826EA367A62D330403E90A0BE83BB4E3980487F0B67A046B2876032524B5F185A6E4A9410CA7D9231DB109AB0EE09B25024F570C2B57192AE98AB0466A20D634F266413FE8F3E702DA112B6DA139191CD5D398ED9D1445A83E237954E9921C55308DCB34EE24B7068B69DC4A7E096A18FD968C2C3E23FE75249341C79366F4806436EA5EA1E7CBAEE87EE61E50A08EA13666C241A2F35B04FA20EA1D52C809F0C30D9D80A8BE6903C93CC448608FA51ED25A02A9D3DF93024BF9D262EB032A16DAA0F16D60BBD333E7E211059947CA4B794D581C56B8DB4E46921D2928C6A9AB5F58BDD0C8D9660CB9304A5FA488F46B54D65161AB28B2354B341A694D0D0BE2C6847BD19A532D5E34129D917CC1534B1EF2A3B69317DA0A5740262DA7626252A11B4F49D6C95E215762B642384373C4931EB8B879EF1FF2355702DDB6A367C9D82DF33539F9C15B38C764A1AD42C627A691423702064EF10C105B54BC05EB2CEF02BBC15F05A6E0DD6208E61AAD7D95422BA752DF35B71EAF8CCF4710B7908C7BDD42E52DE294BA2844CD5F801242BA10BE8A9839C5F4E51F9994D0C147C392FFEED44D66FE8AA8E5DF1FFF1FD88D7A2ECD4780D6586B128F6406E5299C7B5C903654B0D098D6D0C605009FF29249D6FA05015A3D8FCA3476F09097832ABBF8C5709DC14F167F4B256DB7B29199F481060BBDD60C3BA82C1051A5D4D8B6D6C81CCF9149720FA74C54DD17C3729E63D1AF55CF4F29A854D1339746C20B6BFA5C9A6408DDEA5C76EF52051BB31B2EC2145C9CE79F7E8377BA78043CB3E2B64463950FE6CF5865844520276378A55C34D2E861072D6A405FB28AAB1C32E6702FC93A3AB9A45FB51A0287E5286B94525DA50F0E95CDE3D82F6D8B65B13ABDF66B018D760F7C51C78EC19EF0BE026D237170AE0FB000EDC54790357C4015DC70BF1478627A3D021483D6ED3907FEC1094F10E03E30C3EC0A1AFF76120EE48D042E12BC6F27BDB7910CAD39D562B74B056E6E87132B46105AC0739CF50176FC6232AE0E673AF496C6201C95CC46254164498735CC12198C50C925E9259F041445ED012D31E10631782C8D69143A0FA5B0DFF7324D33D0C419FAD821F41392823D7E00BC923554002D0F88179BEE302EBFF417120DA81CA9797D29A92AD4DA741B0E12E81E09CC209C90095BC99EB7C172E1221A208E771B293CFD706660260635DA6AAFAFCEAF97CD119D6279C0EDB5841E7C9135C563D9A6152D6E548A408BC8F82AC2D9F1827653864872440C86B35DD2F3621F23E59D5B005641735AEB8F51296D5D5083DDD16846AF1F871752A3CD23B8F132BC97769B7684AB075F4E2F216249F68B0C9961A63A836684A439AC01EF6A93D49B09184EB0695D38908F25A734C782982803EB09042B15BC7E7CE2FB717A10771C76207E066B945A03A97A9857C44F1312C5C6BB57C8BE69609B1EA9C241F4A2E6063A92746B5E6FAC9AEA6941E78468A9D464473206CAEECAE33BFCB807CC8C548CD557AEE0E63CBF87971005E435A0E0EF7CB7ED136E8850DFDE05C5A1E5909EBE4DBA52E38038785458FB6E2E6582988D56EF366FA168586F2C3A24B4EE89631D7EF231A46CB78F50FBA33F05AE10F484CBA51DACF02FDA944191C074097A527249D485498B3356CFA2436ACAA4E0E31DEFDA70E2A4936040BFA3443A8A09771DE9180AC6AA017E952B8D2D0023C3AA2C70C89C78B7691C36853013FF77155D6427BFBB4FDF338B4F245607B5B2A1D0A6A1E1774C640D760948DE243AE11CFD829BDA12D117898686162EBAD1B41D6E7B2743FD6D3134313445E409B4732C1FD7FF5E03E797E7C758ED22A1A660A56BB8C53D11FB1F47944C281BA6805642E1FD4D46057C1A86E4D300F64FE88FF0939F154DAD8F31224A790F72A83C83E2F23FC93D3855C24A3027C531DC4C92797A33554468CAE2B88073ACE64A8B506260606540F5024BFE03627AF72FBA4ABF713CC7DD6F90FDAB5490544F936BF2F44E1D501DD010415783C04E735A620FF4195B5D911CCB470161E2B88D42A3D84392A814C668FE08754A97009366DCA99038B81E30E5FC53F4A09F2A10E4B8C40E687687FD93B931C2329EB26742EC6907ABA5600C9415D7B9341896DF4508B9C6FAC0D41E92B72604B0307AFA7EB9BB7B32DED98C62713971BAE10BD258B435AA24CFD91956BC7F96128981EDB49ED70E6A20FD233282C23DBD99AB4787E12FAC6EB42A289A5DF4737A9DAC91079D85F4E14F28387C7C2D0237625E5AE3642A8071AA15740A2B11E49C152687A4E346B243529DE6EB9C9780E9DA5093CEC7A0D74BF5581C1B934827023AC4A618BCFF934B65CEFF21DFAEB2151C7E0C4D64F91AC082801078E97BF131838A3D585B661E691F31AE0B7AB306C91AA6269DA4940352BD7E70CB55AE2483B37FFA8D4F6282935834BD2A171D798A924A78424E56F9881F0C98C2D154B32412B0927A00DEC8F42FC3AB694D6B3503EBD5908385BFF6AAF8FB92DC01D9C67A204A4403F89255A9B06AEA4F8F781383DF3E6BA5ABA36DB31DFAC3F8AAD0F1089DBC8148BB1886D27F4AD398A8C3C4D3BB6077C63ECEC218AA3F8D9CD6E0264B6465BF67869A9CC723500EB937028FC93EE67071DCD10FD50AC8F8D706239BAB3987499A2F0AA916C5C98DF02EF68149F28F2C01111A0AF50996D4026AE1A93CE35C32B23FADBF9010609261BD25C8661BBE64F1CB2F1926027E80F4A3466F04F94B34CB6C3E08771210E306CA8090A32B9861B4B4BB9859548C0A9C3D3D39C4BCB5E55854006037C026904829F30752EC29644F089A0CBEAC2A2E8873A081EA9E5B00FD97E77AF722A15809918033B48080F2C8B9FEC4EDE29C636E013FB4B17738CECAD690E2E8160FE8B299E912CA5E109FB53BD663CED0406D28F1D39AD70201906FA36DB09CD5EA20A9D6411811B169A0C81FDE92EA8F436507DEC21086320CF297070932DBE117427FE13BB4BECC713310609CF0D0077743D618AD60761580D40E8169698DE22934613B13CF86243589709AE6D717F78197AD045EEA1978AA11B0DDE84D347670E4F8CEBD5CF44719D1C2E7D2271446852DEBD3BC14B96F9C82F81B1D460C2637C9D435A544C556785A000A00F8A7033174746581AA4925C923BCE4C0CED41402AA1D107A5D7AAA667B19582DDD31864146D75DD3844042C1E994D6A2E2690A53DA29659B09854674863837B013803961074A6CEABBE1A4954B606FFE63C10F5B716409B876B8F4525A338802B34CA2668FDF2E978EBF82984205E2019BBFD865249A560817DB872B03D000174B7EE0452BA00E20840A1338DAF31679233BFED0C4CC538462062079BC8005DE87199B0DD680E9ECC5037287EB18C08BF1691987637AF6D08736D6E0D8905E2E1ED8CED21CC15FF3F61465CE2C808BB7DAF1F21B08C614762E4D3F42191767D41CB62E32F049DDC6D2FC51F680E4D89CDD6C48A9D2492D1696A05C1470EB680C8F90884B604E25C169F53C8A39FBC6D5BA02C5A474712BA56741236BADC59497F3A7A251CC52A82AFCCBE8B5E729115AF56324EC1F12A59F143ED014BA5F252E2A8CE75B449462002A49977F7C888FD4291E24EA1D0ABDFD8BE24494045EDF43264DADBDE2310A51923CF474C1FD9AF84E877F7C745A2887501EC70C394490ADF817B9134F84DF5B1C0FA17942E374D854B73841E464C2AB9F258252EDA66921804375862E6578EAAEB419417BA21488397E9BB68C99F4127491F5F894089F16A4303D3BB862C17BF225DEBB6258810B317632DEB21F9A9ECB11C5FBEA95AAEAB286377E5B12A71C11CC44B2D656873E6903C729402D2912F37387028655C975D7139248E06726DD12667C8074391789B11E0C68EC2011A7BBAB648FB754C20CA7F3C13D710C4611587D9A1B9D404802F8186963400D6A163D306697C072E638AF78B0E852E917170869E30C81F09C5DBB216367C009F6824D596AEA12F54F1074128ABC68E2F02A5ADBA25377BF1F2D3363619D55999C6C00DC8EC2A3053E28E3FA31BD2C9FD52E5A8A34BEE7F6BFB3A6AADECCE36DA5B77B77D7F5B12449CFB45C3971ED679ACD1427C5BFACAE6A03C3C8EB83CB6A73BB6DE3C5028110EEB4754CCEF4644C1E43809F22EA8EA5B2FD49E3783762494FA7D6FF65496115B993581A0283E686E66A5EC6F6B30CE8CC4258CE23CDA81B19839A5DD7A957515960F2875802388F58B7E5439CD06E56445A708E4F31899DA1DDA68FF0EE90100C9516364A1CE8045D7862B094BA4859DE7378B03A30495405BA7D2316EBCDB5C5292AFECA096A273EE3BE5EDAC093B3675E6AB19EC3C0BC7B1D88DE28CEE9377F9AAAF31769AD4432131A73218B3A11FCE061C9F297581A84E1B277A9FC4EC25D9627574F749D3F843ED73A46C8F0390F6599D335B3A7F591E0F9AC9DAAFBD43E9D056E568C3A2DD3C169052A33D94692CA33E784219C7240A537559AF9FBCC0089453129069F62F27D1F70CB3EB0BE9C555731C47EE166EF9500891513BE587683DA88A9AE9C34C1DF208417625B792997E1AED962BD2719541378C78205DE1F0B38813D2208121F46D36C000047E35098410132DCD171FD2168ADA258EA16641272A19A044D9142C7A861161818F8AC23CD3B07DBFA21AF9663C465340272A7E6932F1EF702DED1ADA76E01BC8054A906B8A11E7128950C05E777773E172831B4143B1C55D34679A6F2167DB63012B480E94956E46BFA5A664AD836ADEDEA8E02FCC1D01129854B3107025726862511339BF37D8BFA97D014B43A5F5AE93621CC7E21B362478725C092A534320B4A0C7902F686578CFAC97845964728AAE31EFD4875B2E0EE4738950693A4221AB31FCFFDCDB6EBE4EE727EF0D60E968DA6CB8B503E7CC293D59FA38170329B67F3D4C059BD0C09321F40719DFC839C00584E86C3B7C52FC88332B3AAE13132A6556566F56FFA0CF65F4311B98A29114A89EE48166DA8A7DEA2AAB7586A0338B3E5ECAE3F8A284B4E1F5B4F13BA7B1C5043FB4AA22F226F3242C7AF2AEF552885081FFE62278268C1FF5A3F24A1BE8F5863A867F136F2646FFB231A8C0CC65CBBA7FD0437BA1A80BD29A1FF18F08FC74BA1875BABE3361BAE394AE78851E4161CC354D16579C1F3DA19B5E8C4DBFBC6F625D75527A595969CE1F59764080B0D7882C99884F6F3A8BCB3AB63616804CB6F7F728D0110D827D8995DDF29731D4AC6F2E91BBE034A85F6434E4A6E93AA30FFC349C3ED2F7021A4421842348C90D606096D10E4392F8210BD5A78EC80C9CAC88005F381152322B40F3031AD300A1F7B0A118F83BF17F05E568F468FE0CE9ABDA646125CA920A10FF9020B384A2CF8767601C2A6CEC71553CA54ADA7954784D74B43E6237C8A3BE4CDAD7342F6603E159F4882F13261D43D41F8ED6B392603FEF00498ACAD5DEC2DA528D6460D2281628F8A76946895A18D318184A40A81CD50A90060DA062C8BBF15E4907821E986191DDB63AAC26E8CCD870C0CF1B7BE5C33ABAE6BAEF55A9FD89E59E3417DA25020A08AF5604179A2232F45A712691C3740F2817A71C5B69E3642BF57E35CDBA612BA5B5A0BF05F2D7C65F51785F90ED2C17898D4D2F62E76999C653768A03763E6D85C643FBACB833DBB1716C9F9E9E86E7A9986B375A2D40727146627A3B5B93CDA240E592942556A42834F76388485C1ED23B0752ED21836EEB731909783A19AE2CB4E487A157CA2C1AF6ED99B75885065FE273005435E85EC66962922C4C8C246160A6AE31CE13D04E48A27DA835948B3288E762C687FE16B160F27C54E1DDAF2E2901E28C1690A90118B7A04B4FA87ECBA45494B3E77E183646F8087F3821266E66127C5A0729BCA1D874C8521526966F3CE1094437287A88234D956B85000C54F654756304C23C4E1E979F985E134C4E4057D89710D3CA2DBE0CCC8B96DD989B0B5DE5ED2E85D9BD9AC48CA0826D1B00024C4D788361DB80F995DDBC23DDF0350F04A67F35AD819397B01210C6AB63D98AC308CF8EF0790E5DEE9883D11D9E0AB8C097ADFA2B2ED761C52B86FBE81DCC1C89A73B76D98E3C694099540C2A2941AD89B713F5EF4CCA5477ACBB646F8AB74B2384EA7DCD7A5D20D5B29DBC49B2646732AE6EF051C879C8AB11E16EF80E2241E7852DA941B8042D08EEE22DF4ABB708B2DEEECE28A9AEA56BB7A1654DE8A77D2AE487099EDE575A5316558E3D099A0E6649D3BF37DB4077D807E7D1C82D9D3BBDF162CD486EFCEB1975696B9B21FDA1A60FFF76304A07841C05ECBADDFFA6443902AD94163464E51DC489A1295FA0F29E5323C6CDC7F85930EA3B12B89CA5A808150430B1E6933975FF37993E65658B212167C8F0A359A33470986C8985B82F15FA9E0839837146605D392FD8ADCBEA9BBB8500B792BDC5B0633E98DDCB5A7B50C798B09C2EA5045694E6E1A1ECE77681C278C68A014EE030CDA031377D2F7EEE3B4558151A03CF2C9AFF390589F0CB3F772454395F63E1FC6B151BB3AE6218FB7F3B6A5AAED22A0944CD916023EAC65FB7A47C919B53AFCC0D1EA295B09777A4191D647B95153E70596D8D3671EEAF863FC6A0D956D0EB38DC91A72C6F68960731A17BC99EA47FA484D041A2AD6A861E0CD9C0EE4CF618989A3B146158088907584F0C2B1350E9163465CA8168B45E5288E8FEFCAD919D1CC54AEF5C82973A7EC74C23E3A64D0D2C94D845C360E43A9C38D8E26BCC3BE901DC89B98CA50C71453110E0D587D1BFF8B72D3066C8079C08FC8C179E09CB9F02FC3677A07689F2212B0793FB9303F9414152E80C24EFF0C5D7AEC991BC42BA622D309D2EA4716E31A50739DCC97A6269D3E66B7D3959AB40B8ED047B4FB20D58C7E067D28520BB2F2DA582D9EEAB21BBC66B6B3BBB6EA1F6C5BF1E4B3707C3D00A53F636A40AE882F101916397A32B8502DF50494A304688A189A4BE736F56227A73E4A5BCC68FBD548CCB46EF5E52C65CC5B3CC52295C77777B323BE13C5F0FF5B9EB1DDEA64B4A5DD7E84BB1870AB5DB087056AA2C8570BD444656545C4E41BE2D0AD22D1658372AC4E100524392CABBB49305B21FC643C1090A065224A74118C9D2F90E8CA3B40A21E631FBDC87B8C0A585DCB7D841F4996B15146C709C728BA7682E1CCE7F6D29E387DC918F51E3DC2FFD19186538B7D7D14F5BB021322A88B9BD145C2152424076C513561C1B3EAD8CE7BFD850D1957C5A2546542F332E320A525BED6BC94DC6FAC1511B3810200DCDAD2A22800819350E93FE7A967E0DE29BDFBDFF40E851774B73D93B0F62D07FFA18827E0565A043EA9CA41EF5C5D8578303AC0F3EF83A19F592A216F1E8ECDE9B8E2FD466DE77A1D2951B4FEB21C121EB8F032A2196383499DA9838C802322B72D8D64A837B831FFA32305CECDE8860BEE3AB03EE2159C2D8C083A889D7670E14643FC9620101281382359546F190E0EE29D546D546A0B45F42E4590265AAE23B9A877AC0A38321AE6FC787BC7868804E431D4290805475699860B827981F75386E7CA6A90A1A18C6D9533A72320649368E6577B1D9BAD80DA0F673E0669A4D3545AFB7F0810A03B218BB02F6E69B63DE570FE001F3FEF952FBBF30F85A0F0A72479ADB8BD4ED5D840335DA8D8656D1D39CA0C216DDBD0D276BA8D80DE10975C9EF520E8E1BF01796111FCDC33533AE0A338CAEAC146E218AE2B49F9CB021BFCA75B619D74A5BBAF305C213F4D7D2306B24D00E18A57BF95C8079AF0C31A6B979AEE3690E43CA70AB4E8798020FE173A49E3C855FBA4048E860E6647F5B9F10A05457459B4C07B58817E6C2CF4A230564F2020CF6D2B0DBAED99805E5BFEA9434DD7B45C892472D12E2E43D6BD13DD3A81C045BD6D8927295CD5D32F06180ECB43CC76CA0A8CEB5B0296B5E80E35FCFA4599B2DA64A3A2DD63ED64F680076111908444B94E124457654C1EA646638B8018344B4B82D1A6F59022C0E8817FF891195663F8A2AD043F6F59199CB17C5CAA9EBCA040BE65E617B7FD40BD0E97B0B66CA53E8F7D4943822313623F3748F61D4EC04396B9C01D2033CB097FFA4C076D81E82264336B1AA73F55433114D52B534A11203F97CAA7028383EA61807B4E88F85B491562423094C517044786A09EA4B808CF67748E34A13BDCF805BCB3A2A0F4326A8ED902ACA98A3456B3CEAC037DB88A0488E9973016B28060CE70378A86C51523071E3F4B91B08E8D97C0C9DCCAF6D3A143ADC3220DFD5C75301032D9A10D01E4A2AAF88E8D9B39ED25A5B2E9E336271E7C3949DA8F7D70C7501ED7DE7A400C2902D242BE5A78DE6140D7B4D1B625462D5254E9D83130CB7B5CEE943926B3F33B84F0FBD65A8D03C309B4F6F136DE8DB9E6653909EA5D65D6A5065514AD5B2F84509D456925B2C9F938D9D217F2962855D09AE1C836E54AA3C125FDFA020CA7DCD50CE2BD712EAD879A24533F8AFBB1E0AF0C54D60935E7F87BD9DD2CA3F49554CED5BE81897AA398301F7C0FF2894680A0042B52C88D8324E4DE5816C81407920E2A2E70196C0C00028C02B4315C00FD38BDAE9A72852923A77BC03FE28C07CA3CCF8087A22924B0FC18C9BBF774F55B8A5E30FF5478DAC9B28D390C7F82EB327F8EAFEF4AC0E9E25C9316F72E0D2D985C84381F3EAA924500A86CA4995E68A361A2D7CCE7DD05891007FD44A9E43776963DE127679D07C1E6D3BA9D4F2AD943244C18B4E869D273240366958A6E588A6CBA3E6F8D4F8E1C021D0BDAAE3B40E92A7B63AC343094957F2F4517AAAC843EA56159AB85948FA98DCD6BE81664C2DD2C882DBB91C9BCD29C1157C2C47883197D64179F12E3CB4BD0B778EC63DE092DA4FEC408045780D7F15C2712ACE9701802D3B50449550915701FF00F32CEA32565299F5137E6BFFDCF2B1540467712CDAA4C87CEBB42D7088AF4EA726B583449223E01BB125AB9C821B4F824F24791BBD01BF252BBCEC50060A69EE1F977BA62CCE1BF92C2181963DA86F7790A34C367A293F459C2EC4D488C9E574D1704D8FB112196F160C046E4C4AC0F6EF271AA947D88F7DC0EC7D7819780F3243A74BFAA6B66C2A9322D959219F465D8294EAC9A12285B4E2896A87F2E0BC51295C553E323F6D4E133FEA0E5E545FFDB971D1FF36CA1B45B6E44E1213DE0461C5CB01272039605361F620D431CEB205AA6A1F07F9FF9A4B2F0963522B476600A619C4F40319CEE9DEEF8E6635BF4F84056E97102329ECC6DFB80DA921D3B57122B8E5D9CA835BF4CE7431DD5F659AE22D3130F1C6290AEBE862EA4AB896C5BCA7935345D605A12E9EB45C21CEC26C2BBFA5E961F6CA943724F384319D252DBC66EB9210E8CA0032F5A54EAAF856AB7FA10F417310D09EFE3B44D9E55918141388427E5B4883B1F06035EE69E2A04D48D258C730FF178CC73B40415C9324723B59DBF07FE376A1255405CACD341EE9754024403E9215A73C33D9199DC35D3F9788C40E8CB6443F2040D216A522C1EE1612127D04454154255B501FCE244C5FFCD92FC7FF846E34CEDA2BC1F4A2132158C06A0999DB4B97A8E7A91751B750391F5AE003859A13AEA355CED13C8DA4E7AE3755AE942DB818F8AA1FD0B485131A53E823B81C316FD0D1185A452EE57FA09514B6464E64BCB92642F0FA43B71FA30C6918111053F957A91B41A478C7663D7A389D7E206843A83CE3E05218564ACA2BB6E1A283F3CE0533483A4D14AC54740528394F6158B12B0EE1D28CB26AE56231BA4EF9F3890AC41CE4D86792660E67AF491B46A6324954149AF79E12E452F5353F75E0A8DADF3C0779C3E63435E1438C320986236626000CCFC182896DC07CB36478B2A8453536277EEABA63E2E0515816D03C6DB95A05A32B984D50B0633535B2BD64AEF002B7F60C9370F844074715FBA08D34FD800F570F80FD269895ECF007BC37988AF09D14DF303C367160B6D09AC1E5640FBD1990272D363BD8EAAC07C03AEC98C49A80697B2D09338692734DE4B928107545AFC9CB62E081EF19ACD2688D6F29A3A8E6179C835A5118E129604BF94A75D08F96EF9591FF82DB016F1B8562F4DF888B170D175E50683AA19F4E94D8B730EBC75F50F3345DE17F98F73D17CED558BDEA0C4C689C3B4E3A46F3001A28E62B2CF337B5C28332EA5C046213643EE8F45D416447E84CE62AE4F6F853F068FBC70B821578B8BA41C3C5E0BD6B110A3B6CB0EA698D0010A45B8A7594F8077BE609138EDA8903AB3E7BD5AD6DE2D237AC92C7300076C3180A89BF8526EB6047B91114456FC813C20169E4E1BDEEB08A3FD15C51FD010890EA56856C42494C6F725031E0189F06ED80967707C22C9762C98A43BC07BA4CD13F911D6F342D3D663D5F60F9FAE435942E0DD7B64D197A082F080500291D79EF923A9CFACDDE43F059312A0DE76E7978ED69C70B4962ED8555DDC11452D33B163A508DC1DFC7940BCE7974B549A906F8B6AAF455131200E29EDC113CEA11D9F82CE643DD4511BCA5FA39DEE315E483601CDEBFC9196D92208FBAB0155FCD2A09C903F0018AA9B3913E6CFDCC307DC2B6A25FC7F21154821BADFD3A0252ADA968824AE2B88498A0948AE26D421AA6347C28901B5A437C1988FD8F6558A2A2176DCC41F6D42C2BCDA8374A0F3428B4E2B04E630E2E53E791028479C1132C37AA04E35A83F74C0BE0B71DC67B7E846E4B654972863C9D011737670C54A94E6B87E0196961B831D8BC89BE14CED0D69172CD5897AB87B2C1443B8FC46A7D8D91686BBBB1C1768B4D2439E9455271A4205C6F5EE26C45C627CD835B4CE0ADA396DA301641C7880E28A3145C841939B59B08F02B8C22C9D8EB02F4AED5E1A575DAADC17040B93B9069C10E985AC22DDF080A44458904D87A1AA106DE4B93F1648D36ABC89C80353C5C261A96C976AB63607FA22095C088C62C46550D7D15571076BB1F55A45D70D55A146EC430A31763255D290A0A5E8E7B5E366FBC616D528D022A39F6611723D43AC3E212B880F970E2196E692F8DDC9A09C122C5A57E6ED8A5EF4FB1F3ABE1FEE93D8FFF5140E0E096F38F3B94A53ED8071F544C3EE5B91CBB4EE9DBE7757C6736D83B6857D8ED8FC4220912E0495384C99542FB2D4A397EEAAE1D7161DEFFB35ACA0FA6FACFBC3F0A49018AE3A0A3D2907783D3186F0FF415E853E6C2887E01B033183210B8B0D910069CDA654AA6473F83C8CB217C6858369F73FAF6FBAC0052F5905839EAB7F55A9AF05E4CFD311DD2A212AC971791E6F89E621CF9FB193341207A20901597CD39EF0EE43D182B9AA11BFD18FA894D08522C038762027C49F37930A6D4B442D8450101A265FDDDAFA9138352E15C376068270769413110DE83A559EA8FCD0749B760818174207E713A80F54860CC4248A33721F57E531B579829D97408EB3666E47BC3407608EB076586801C686576440DF0FD0814B1704FAED76F01E8A3DA8664A1B3B070A3BB214C006AD0ED54B7C1724B28CA86008D961A094037EEEC0CF3330995AFF8FD9E3CACD820256F0D5E21B788ABCAA5282D74B3442581D18F4FB843F238AAD72B25A5E64376C2394ACE2308710B1799B0586040A135F5248DC0C9F0A6914C51C9FE1C9A4AC6C4C4D2A71E7535BB9643D87B5184B2774E1224269CDB787C403A9CB9C71B764965AD754D58601F29E1D403181EDC29A57F172EAD40723DF6DBEE0975337A0ED3CA20740359A892758062E97AAC98A6B9422BE031F119D910083FE7D780F50D55DE53B502C7BB623A8EDAD819AFA8FE82BFFA63289697795479C88396AC59157D6A5604350ADAF6975C45BB564486C46EA20E61F94CF3E0168D3CBCF3588B406AA1AADE22CF3129E91C998E62EAD3211A851ACCD13FD8232AD344C4147CDD1C338308130ECFEFDA8746ED0CD790596F142887F3A1B4F09C34B4F0327DCE6E46BEE1265D6C749622A1A0619D363FEF1390B5BADEC4D204B278820386BC20F8459730512D658EB11F150C58F2E01F21F262E3609577C80C580D0A896F309DB48EB998EA9CDD84535483DE96A46CA9738D91E00899436C24A32B6373616530EC33BDC39404ECA1682DE771BD6CAEB399CA3AB0851C4A62E6325DF72AC6EF548FA6E30584F52FA82123A72558C0E2FC632934924421E808FC306556BAF84E1B0218620FA3CAC343982615A5CB180B61D659EF1D26F6DE302EB71A3B4F562C22024018F2BEBA680C07957FDEF2EA4CBD813D786D1665A41F4723D2BAD5220178355361F0C2459390F2A8DF121AEAB228FEACE5702BE8A24A2824F5F710DD6E5E52301B152EA673778F0BE3EFA55314F43C577002ACF31E0FA904EA130712268270D867EC9A40E5671510FF23D0F81F70FED27B8EFFE44EFCCAC0B37BE7BDC146FD4A46D8B6F50ACF08CC21FBF17C44FBC0E2382D3551FB7B91105315BFC032004F968F31400B7D418778F52903149E143A0452DD731614DBBA337444417357DB8E6372BB5447715841952D7F72BE2E8C7FF56B5B1C017A4E83084519676B1F8345670B5E7773CDF61905CF1173CE0DC3703045214E6BBC32FB7397C6E0C6669001ADDAC54B7E29D06486FBB650F06ECA51385BFF5EA4CF84E3CA90D1F2646182F6977A93D4BB41BDCB4AAC7EAEB82EB80343910EBAD848C6DFBB70CB021296F47EA773F31C03ECB6920DF8C904D00640F8AF6A20E3194D5F42CAD2EB5DA0436478F09398FEB609F07C930CB756005A30EBBE192025614C409D84CBEC1821D7477814C687F4A8A7670C54161A222EFB9C09385720AD26F23EA7A1BE11842AD6CB7AB0C931475AE33618CB7F695AAF3E9DBF5C0689D13BF78DC4F7D0DD50AF00BB2328A160B26C5C8171C9AE61271321A6A54A5A4AC45A9682D4E699E9876BF7CF9A289EA0F6CE572FB2DF712C54C78735DAEE5AA413F808B933E82240B2BD937298A6196B88A70EE8CF4FD8C5710F898F61C186476D4BB77457FA71FC3278D7CC93100E124F597322570192D14EE1FCB65B56879A2CB8F7BF784AE8D1A66330BD9257443BED893F38044024A4D264251FB9214DE3B4378D4EC413644D751E1C96B3202DC79643164DF41E2591110F85C1F825E47185F43919F3391730E120187E4AE521CC75320970CB99CDC60E0C6FD9B04D33978683AAD36ED35B8873420D2C0297F91C4174F0D6B448EFF31286FB6ACD30E71C6B40C33084B5B9BC1EDFA241294E7841A7C03116922A2DC332D41284C0A0C2D85D89358061E560D4F634B0A9145358BBC9059D1B51E91F959E25815AB851F676A2C868D8E1A4C52CB6250348965DCE3DD13ECA9BCE89ACFC748559879AD070ABE3489024FA8EC9009232C7C858A22D559FA421F19FBDED910DCB6209E45062D1454B51DDBFF1F078A4532539C43019C7866ABBD5DFBBFF8DE61140F9E59A46ED3B91F83E1F2A20AFEF525E9BBA573A2D8B8944E9F350635232426A902329F805E590A49A44B011FBD34364378A123D086989F69639E77B968EFCADEBAAA00E59790681F624F8E32D7D31070430C5B0775BD9CC1D2CD4C0FEB8961B0D3E6465F3AF9EC89802B56C748BD4971CAC0AB14889B952AB25047215AC93DDABAE2DDA4276CC9EC74F732381608A302CD1AD95BC47384DFC13592DD5795544B8FA092A176C70D1148F176C8B1780159D4B1E2F9B264731C74BE745B3499DFB3D8980588E66C548136513CDDC14731FF59256DCFB696772A1480063BFB31B158B17862D0C913F0949C0848A84AAD08E456C71B4A9912242991168BEFC1B788E1CFAC0D29402750CE40895B536ACE81BA85AE7490FE43D2EDBD1D0F3261463612096992D5D8304F5F50BACB94447CDB617E62588916365C1123323F52C53D9DB7A8A1DA4A21F90A9BA345838285A43BBD1AB43472DF326437B8F619CAA6CAA6698FA223B1777F64FAC391FA387B4807A23AF218309F952E93C6F60108781003EEBA89F522FE0ADB82C1128EC57DA4E652A462FA7745A483A3195B8EEF2ACDB16B123418FEB3587490104861F0AF6E1C8A66A9FE3FB002EA26C3BF4D4A6577F651A50D15F53870305FDC40C610282DB9741A5E062D91F7E70034B4D5285D9EABD5426FF6DA4F4CE4F50772A0A61D98309A6F2CADCB76985FC00A570DAA0770207AA76D0B9988816C1FF5E1AC4B8C4169042916EEB683DE186DD390C49946C5A02CF5810888A369FF41FB1EAB5D28D1DC1A2AC0314D81EB5363C97D2FB6267D26D34A3658E66517CDB1F0BA512338D0C6374473C004FFF464A882810D97FCEE3669B6407F8CFEC4947237AF003CADB48E1981D3884E2C5907054F2D32B9E797F2C1BD271DDD030DF634E45D777510399D91EA105523B30C917407220F67B210C4D8EAF1CAFCE19AAAD17B8C803D9FA03A0241CD392083C0B517F3B5C17A1ACF7FC570985C303A562722AA0AF0800C633559D5801A0A45AFD4C4AFB1D93237A3870916FEA1BC05C34678A42F00BEB1BA03727C9BE887E7AAD67DBD192A31E6FD204E86922CFB3A27926955B96AF2AD03F781CB29A483C143E36A9ACA2C9F8354CAA89E96E9F03958E5BBB905E037A1513B0C90F7B9EE55E0CEDAC0D3A078AD5BE57EDC90FD10C2164D2EB5F0A8D92E7EA3217930E6648B0C48C23BDE188E584F48024DE64BF7A3498528EF0F105C6601A9810D4887D98FB940B27458A0A3D10C7808C778680401BF9B3CA4D40FB00582E6701311DC3B3FE34C5D28C5E27FA0581D8372246D253618B37602ED05B360DDDB03B1F29E30AC7D35BA507B75B1CE915D6C3A8567B01885C394CE083C213E977D06F8C0E34B5EBDB3CCD0D30849F3FC3EF00316AFF02DE794366FD1E51BE8C68142C8E673F8EF7E7C3357C042CA017E97EFCE31759AF3431CFBB9E5618383B259FF449A577B18C1475D22184BE92171A3D3B479B2279269856D6308E11341FFAC0294875F4A25A6616002AEA328BD9E17970EC589C7EB5BBEC2E57F94962B6455407727B59187126D2C84A17706429693885EFB98302680E2C4B391E6C5FBF14B0E1626E1914E3DAA24F365179A8B41AAE02C44A03883EE6F2F615286B47F0282B82764EEAC83CE825B704079B4A4F608DF52D5BC5BE0EDA7CD030402E4485569B8B4C6AF9EE25C7A6FB78A28985F0AA2964828FE648C60A1C44EBFEC99BF514A75CF170CF5CBA2153199E124B8CAA5091616CA3084E01AF0FF18C3309FBF9C0C52E76B5C368B5767FBEB09E872ABC9420D10ACCCD679C02DCF86726EF0D348FEB9B4BCACA463E61DE6B20D7AC1C3ED649704D87A56018ABDCE091A714B88F88C3F4E8F18020AC56F1357C0535615C3C94B8D912E81ED5D77F2CE121366B96F5290BDE36B7FD8FF01A4BC0A0005EE4163828B9CE06542DC11DC0108C5EAF4598CD07D6F05157A04FA7A79488EB6D429CF1BF6A45218C93A1DFDFA016900C2EAC81840906C512EDFD1828C7146D14E9993EB14A4EB3F6350D27B502D4B0D65547A28DA8C0BB46D947954CF8DEAC0AC65FC2175EEF882A4344559E4EB3A22FF081BFC52CD8110B8A2C0C9AEF20A7D89386888F6C061CA6B5F546B8F2D84FC42335BD6B0BDE86E6517FE1EBFCD5E84E6226770C41A903BF094D1F32A1C48973B0E257C281B07C2850B85B20491EEA77483EE11A357A182D9581888BC358834D349D1B77BC33C9D8F5993CDED3F3A44195B6CF18E3244E18C6AA9284EA677FB25B1363848D2545A7DE701A7DF13E59FC857B973B24469643855A0B4CF1104F9CA9A51DBD1596069E2CDD50E1536E0086441958BF43C3CDB51C236D36C9A1C64AD34DA5CC4AF4DC562CDDDB34100398F4BFD5E46E1A163ECB2B3905D28B4F2A32367AF138BE4C2AADDD341A4D1313958DD26E0FDF136EF84A60314C1530D079B453750CEA10E26D0696457CD5E7B601160C55DC0598CDA15EC094CD07A986E04D69C4464AFC7154049F620428F63088B0ED525242CDE9F6D116A7D150E6A04A51BAA36AC8EB33124849B255FB3AEFF612494AD0652208C2490F0A42FA55DD3CDEA6566A8074DD783226E7308A15350B80288104250A46F8351F3BF5ED1649DBC1F6CEAA9548BC0CAA19AA09B43562DBFCD4258F72B37643CCD8F9766C994D6A15966DF8BC281C942BDD8F94DC038C30F639A77FC2CA67D723A82ED02C38A3ADBC13CFBC403465698C90952CFCD8D0911FE18807FEABE9FC07D21D8C4C0A15B2A13061A282C64EB91945132D0DB70966273E20A0110768717BEC389E8921864FED429326935B960262880C0759478FCE67B3A6B3AA09839863102EF31D72A4840141632B75401C152FEC0C9B7C87088D67F70B339384970618F584325A238AA52034A11D4E2185182B5E4F778E02BC16279CC56BFBA0B93DC30ECAC64C5F121D4EFDDA6F36491F4E8BBFDE0AE82007BEC87AA960F0F4CC51C645CCF35DE6640F8B18A9BEC0497948BE1B3AAFC8B5F9DC6138297CC9B47DAD38DB0379AD9B5F4E289C20F1D34010CBEB012BFBF4CCA7322A20D340FC9B96C1C50F381D5565FD680A2F91169ACCC136B243E9F100BA3B2AD484D3CDEB999B3E686194FA19EC6A4BB71883BDC965E6C94ABCB2AD4EE1F3302C2968D7794424DF8ED0862AD4F59C99B2F9678A623FF2023FE2424250E9519C3C095FC83B6206CF44AA8207407AB5A2364ED9BB0904AD53A3B12F467C0C61319C8B85A7BC10D6E2C30C10F271A7C254FDC0729018B16B734039B7C9353692416DB1FDEAE1F6BD6116021BC884B10C1E8F4B1783451D8218AB372BEB89762D127372F8E276344119B24F35B04648A755B0AB4DCB2B920F6CA7E2C4D4C9D985F79ACA6D7A09A154FCB4393D4ACC949E86D21DC8A5B60A1E7F2AF3B5984D3CF8238EBB4C099321B0AE61BFF564CA411C2324A64491B88442916D6BC5E5F92AA5E445BBC9F379DFAAD01CABB1CD16D3FA9A0DAE3360817B3306FE07399985C0184E2EB6985177433BCFBDC42E08C90E8EC11C5033A1D9294EBB0B8D34E6668E1422046E6A3413EEA78344FA0DE3E929C739B5EA75DE9ADC43E5AC5C75DEC03679FDC85434A38B981C8BCF1CD53006D69416CADFB1C0E89AE314291E50740A289A0D035850FB0CFE2AD082FF6C45A572F549CEA96142C2D06855DC18CA64FA80FE0BFB9C323CEA8EEB8887652E2926732BCC85D1874A58886C6955322966F1B5655648309E3B2A26105CC819A33ED2477CAFF9B83271E50221073828070A4F3E33CF919B64469E0B8A893D24DAB1762EEEF8CA43C64027FC5E32D701C9736C9CC6CAB1269493D17B2875E4AEC2CAA5ADB052D341F5820E31E6940C0983560A5CDD816C2907004D2FFFEAEAA5B1E5F08A615FE1FCEEAFC136051E40E360D15C10B01D3781CD684D5EE9BC642D07CE4361C7F588DAC40CC6B80485632E9C1A46BCCCC4D2CFD39AA00B34E293429831BBD56C2430EE5614B96A5C036329451001396757D59204DC2EAFB5DE7BBFA09B4574E09D96F8E4CC4387E97075B436B7B1A125AEFB483ECD818596FC393AE6C8E9319D8CA5A6B55C64BB4351A8462E3F7050A2068AB58BA7D101D25822E70D3FA4FC4EBB5EE373874E400313531DF74A84C8239C464C53C334C359B34025CFDC4743FF229AF5391A6CE1D928BBBFB7FA1245902A2D4024B04736D838310254786D33BC97D42317743DE29E15971DA694059280C8EF92D80AF930A67B529DF55799CC0CAFAFF440DB8EE20B05B62E92FF447786F393416ADE380F2F7448C26CB5245DC51D3D2301AA12BBA23DDFCA473030F0EFA1CEA7764BBB5DB7212C4BAF1ED33501D6260CFEDE17A65291C1A87950E0279690E26472321AF5A9EFC5420AB4B860475C9DA1DE8F981CDFC79AFA684908D00F83D3E3CD1F07C9DB029C842796085D0663F4CDB373FF6A08002588EB1F77FFF4B9CC1A010289C9FDAF2A4DFE46F873562D26377F8B38104496698C6BF788F5A209A991B1ED84655462E3C8D29B83A99081C59FB3FADE0E9C75089D64E0DC93E8A58E95E4338544A89EC42AE145A7D95F9F590C8250F5D3F96ECC242B1870DB664A26BDA04E1DEEEC8C1BC44802F1D5C7434BC24759991C272836CFD0552B422AACEDFFC38C1A0F605E900903950136F97EAB802E392BE90451F25D04806B835549B113F9D6CCC88CF63E9F0872AA74FFE918D79EE2A2FAC79214E59023CEAA90E5EE5A34EDB93B9829DD672D1E3CA3BB159265BE00282183F28231309AAD2066FD6B32F0FC759980C905C50E2CB70546CFD876810490A40C29301C29A2C4DB2494D2DD5E3FC23E6AA8DFBDD820C41B1EF24C815A140A0B5950E22DB2BD7D74018A7C62C72150C95BD1AF7A5E88D244D5292102D6BF9719F9A6ED97B007DAAAC0B957110A4857B0FEAB94A0AC86A1A1978D4FA78E093238CD2C1C703FF256741F6F88151AC4B0DFCE627FD95D296019254147E5171BB3E59E6820D03F1B00B8116A295E7791B418D459DD57A21CC86500252DC8AB5B54DEB4CC7EB095FEF08AF51164708C54246BB912DA8EC424A6F853D0219D41C281C75C80A5258809DFEEBA702392F79E22100345046B86A17877B25E6DC2F8ABC1C53518247C0DA07B9D62C6C8308D61102C07E9D8B6CF3500A2830E186768D5385BD412BD0E3813026673FAA80DF47DEA18A8428FC1C266F30E93BBC2EAA32984189D249EBFB1312BC033530156DFF0C245B224E8F9A23A16AB089F8CAC35740665A2CE53847C559721D2903E9985D4093C6BE582054551422099DDC250581ECCBCDF9A89A062A1763214ABDA2A15E61B822E6BD7EB38998AC120E158233C34D2123A304111BB015A45C33D8F0C5DA622E35F955C0CECCBF9518421932870B2DDA10E6D2B3521D43260D8A19478C7DD0FFF96A51466B2E2701AD14E6D93667E61E78F1B89B3CC06515DADC48D46786742A71F9EB99B32A0FD8191EFAB70BEDBA9A07A8BD95FA35E162F5E95BB52666455C7214DF84BBE5EBD6285EC904D7F1F8B5B43EB7137FFFFA484CE26EF63DAA836137EB886B873514B84CA4708C7AE3F2973D6F2AF0246B99C930110CA9D456E99CDF275981575BBC2CCEE86956D971FF8DE8099BECE101DDF2772C266C2C5F2FB010528B21566B6CF0E8160DD495D6104CFA36448FD077124724C9C084A737093ED6ADEDD846F2B640F426C5D10E30598BF08D70CD114B8C8CCDFF36EED37599E6DD630C6AB7A28B23C55613AC4484A1C53D5B911A570F6E2F45122840929CA2E1BF0654ED678009B835F3425110E3EF2B5D746DB38024A0422BF2468F8F995C02DB9464E7E7D3CF71D677ACC109C5CF90DDBB58D2A096D30487B61E8659675E97C959CA822C92D872102CC6B90608CEE167D9B132D23429BEBCCA3A46301DD7415474A90F274406BA79343AA36A1DE8340C9402D4E519441D861478F0C5C3AE06C2A0CFB50436C473E2C2E2DB86A5828CC32884022D13E8CD42C09FCE8AE291228224FB5052F28F5A70EE2419E4A596E15236F10C36FFC09ACAA7E497BDB1C1293F2C7C3225D07969761B60198CB4C662A8E50EF8F5D8B9A03E7F93DF84C727A3A005D82ABB842124C87B9DD25800525B5E6748D5CC3932E8EDD092466201C6121A55F3090FCC3F81DFA48341BFF0A3AF4A3B38E73413E979C14AE201640E7E5EF7191037900B2331710A9AF82C5FD70CF9AA49168004531033593684FABD0190A64CFD2B458A9FD4B07E54BA1CE053F093841C84736BC28C5C4E050AFD58521FF9EA090C040B3ABE2DD4AAAD0C197921D03C3543889B5D1C7AA9850236A3B4DA07EC2FBDC309E058035CF5241D239BF090491028C1983A3560A93B028017D1E9538B688B487D0ADA40D0B71B6BED780A9EEFAB9402DB6B21644FEEE7000664F1AC6BE81900873C92345F50651C15901B41CD0A5BF5C742ED5D96F651582A2898A350198525AA32197A3C232A9B383DA9DD8C81567AAAADE83C66487DD6F24819178CD280252CC1AB5CBE515D2FC757AA9BABAB3A4F45AB0CB8682E86D3DBFA6084A5B77E78D17810ACBB55ACCD7EE74FBE624EA81E8B1804BEF83E426B76E004B228CA84DF4106DC33629D5CD45D4E345ED466FD3EB9E35E0F20D96FD6652DC6BEB8AE21BF46A1873BB6512FE102CB825FA4DA430A641045AD787B770006256DC8E845B33EB29FB82AB6D96D988FB428A25C61639272C261F2EC32AB60DE093C66E71D73CA586BA027DAE901839AF404EF7E01E4D2A30148DA9802C2C91DA78A02B41423BBBB011C29790ADC46FD4A488D23B8D473A21876B204ECEE0A50AC8EB5403973888265090F4A6F6F6ACB489CFB2173154928B19C849596F815B06EA270318CE585BF748BD8E1218090107B9887F83AF018B6A4E791247042C208D71A49EEE71620BACC8831398C302C752F95E4C79BAB3709323C04036068797532393509B6ACF47ACFD5F2DE0684C13C7824D6FC8984F25400A0E437AAA3F3556909B093709F5618C3301DAA5F52F2F3202F34083C248031CB88E54E138427A863959135C7F61E95A8BB0937B646F27B471772B78200E2BDB77CA6F5D373C2A7BA08EFAD330AC8A08C4810A1AD52E6033C738B15E3B471A9C550E26061A6947B2B1B36945F85A205C20B25CF4730064A1B05C5CF1D4453498271D560E7CA4F3868BD279CE2743464B58F38480F1B9728220630D4F09E09C32BFD7AACF46F0C730DCDDACE8FF2C781A929A59C0AF4E237050F418637353666B90B574F901686C18088990E2BCC1424327800C630641C315D89E2DD2FB6ABC7385077E1BBDCAB92C20FB27B8D3D1A34E25284C8EBAC0C00CA0791A9F443C53BC13FCC8C3C1E1F40E12BAFA69E1661E17F58B4F05362511955DD23ACD62A3ACCDBF800CB1CCD8785EE66CA60514DB48D6EC5CB9E3E64ADBBA43FEE5AB077DCDE6D295796330BCF3FA9A0D58A437561CC9C00D6B3230418CA4D5E82231C07624495EA8AA899DC8F42E5E99CA542ECCFAFBBF2E8149C824E466E40B35141F07332600C98DE345C3DED7EC0BB3437ABB9E14428CDD5E3DA0CB3FFA21007921CBF1F32B04E7372D4AE4F83D557887D2678FD6229150F64E1DC21B34EBA49A2BA7C3F02D586E53645828E1AE7E42FC9931D1FC61D391ECDCEB3EA0A48ACD2A6BD6CC69481D76D024BD139041E16AF196BF3095A59C79ECA55DA7EDEB74C99AE836BC4CCD81F632C6258006CDE3BE0AFAC92386BC841C2291C7915BFE630E1D3124F4EB90CA4FE87CE8507BE49394C7AFF19E33E0CCEA628477B461641224D3B05931128C4D34C57E65F2B8D78E050DE20CF506B1EA44E345250A5267A0A61576484A8E92E43B4306AB2F3E85646BB386019516AAE9927EDADEF34E29C0390AC4203EBA0488B3624A34FD9927B31B5C606170EB94E83A91BB284E1C8CDCB0F4B360200AF4B659943D388653F85C9352AE19488DB79A2FE1305E2BB8FD331A397E1985743E789E4C63912E06B14B66C82285AE4357FACEA39E7F82083EA60194CDB07A7C7700804527712F4708BFB430C38C102BA6BF437C608A61AECFEC2C5AFE516E0C9941884DBBD1BCC3165121209025A39429A312A03C1DCD1A69B0DAFFA9246FA3F869B62975FC8601162B240F677CF56C2158BD5A9D835EB8E07D1A8DA24A7EA24723223B56543DDB4B42BAC5CCD94AE87333058CE939E590599DF0F85A3BF02B02B67EB303C59FB86195034D560E225E70F5E55B4F43386004D9CE6AE430C3AD91D76C1CB4315680CF71C7F2B4B86068E225CE232342E9FC1AD173721A3CB94FB6C34F5E9E00A68DB4D3B597F092020E51BF4F5BA6C434450D896274AC2A021C675DF5E43868169573B0BCABC9B7C79C781F0AC0C108B18773B1EDA20E8583E59048E5D4A603310C94687BAB4FFED461E045A4BC3A3B15744A5188337A98555C70E11F018DC6CC6DD4A3EBB5BF00CA901B7A207E6C021C66DB8926D09F9151E800878979D15A2ED571750222E095A4A1546BF319561639E988981F5513410CCC60EF95C5C6768D83F980423433E0A4322458E6B007F43AB7342969B99790F094BEFDC2DE15FA1871E494B503802BAC8CB842563182321621C210975CEC766A0FFC308D808E5BD8D56666192144621A208AA516A71D3F9C2CEF6F02511900229B46DF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BC"));
-        byte[] sk = Hex.decode("07FEFF0702FB0106EFFCF40401F9FA0B03FF04FDF2FA0F0201F40810FC02FB0D140204050CF617FF00FD0805FDFB060007F2F2F6FA08FB05080504FF0801F306EEF5F8F607070202FDF9F3F6FAF5FF0703FCF1FBFD05FBFE03FBFD0509EFFF090BFFF903F20002F309FE0302F7F3050906FE110207F5F1060DEDFAF909FBFC1502FD010707FBFEF3FD03000A0301FCFAFAF50500FE06F50D030008F1FA130006F804030AFEFF08F5050800F603F60700FB08F80D0AFC06FF0FFF01FB00F7FE05FEF9F60301FBFE0A04F9FD05F402FC00FF09F70EFDFEF7F10808FB01FD030203F3FE0EF8F5080207FFF30D0201080A03EEF60104FC02FEF9FA0A02021000040705FEFF03FD06020009FD05FC06050204020A0D08FAF9F1050DFB07080A0405FC09FD1003090E0C000703060204FBF806000AFDF7F4FE0A0104FF04FBF6FA02F7EDFDEF050710EC0B0209F90600FF0703070509FA0CFCF9FC0304F6FFFA03F3FE0202FBFF0812FD1409FE02FBF5F80B08FF07FDFF06050209F609F90BF6FC0013EF020E0F0B04F4FA050DFF0BF8F5EF0AFCFEFBF70512EF10F505FAF7FFF9FCF7FAFDFC0808FE0A030CF8F50C02FD03F108040612F80214F6FC030C00F30B0EFA00FDF80C000603F809FF0304040AFEF807F60B05F5FAF6FBFA0A0A12FE0007FE0AFC06FE040606FC08F9FFF6FE0AF6FB04F6FB060C00FAFCFDFE0800FBFB05F7F20004FDF6FD02FC0210FB040DF604FC06EFFCFAFBF7F6F9F003FCF9FB050801F900FE0A02FF09FA0203F1F900FA06F501FA10FAF806FCFA03000908FC020AFCF70102F9FE05F202F2FF0900F702F4F1FE06FAFBFD08040705FFE9FCF90907FDFAF00F010B010B02F4FFF90FFB0401FF05FE08FF02F3FDF3FFFF10FB000FFF07030B0205020D0B01040BFBFFF9F71AF00306FEF704F3020AFE03F0FE1801000AFBFAF7FEF7EB03FB08060A020109FC0E0107FB020BF80506FA0CFD0116F603070E0808040AF60808FB04F903FEF1020204FB060FEBFD04FF07F8040708F807FEFCF9020A0A00F6F80A0D01FB02F5FEF40908F401FF02F8F50B060205FC11FA0404FBFF0305FF01FD0303FC0DFF030A0AFD0009FD06FB03010BFE0B0A0D0003F3FA07EEFF070008F7090206EDFAF9040709F50D07FEF50EF9F7FFF8040208FB03F6FDFEEB06F5EF03FCF506110200F6000D030603F3FCFB0E0EFFFA02F504030B090B02F90806FE0D04FC06F6FE04F90607170106FEFCF6FA05FBF0F6030600F4EF03FAF802F30004F30703F5F8FD05FEF0ECF9FC0209F906F5FA05070CF60FF709FEFAFB0D0305FF0102F807F703F0F40D0204FD02FDF108F80D0CF7FAFA070503FB0307FDFAFEFE08FA02F80D0B0AF7F710FC03FBFDEF07F9FD010104F1FEF1F4090507FA1101ED0B050212F80E02FE000708F5FFF202F500F7040DF303F3FEF4FC0D050105FFFF0A01F4F6FFFCF6FA0001F908FFF3F3F8060AFD150107FBF3FFFB05FA08F6000DFD11EEF9FEFF000CF600FA020204F70F0909FFF3FBFD0906F7F5060512020803FC0001FF0C05F8FEFBFBFEFE05F90307FEF7EA081105070102FAF706FFF41407FEF901F806FCF105030F0100FDFA01010807FFFB07F50DF4FBF70D01F805FAFCFF000AE6FE0D00F80AFB0007000009F8F8EEF3FA13FBFCFD05F9F503040EF608050CFC0E060304FBF80E00080901F7050C0007F9F009FCFD0001F9FB180EFFF9EFF400FA0206FE05FE06F6FEFE00F207F0F9030A0AFF00F6FE01FE0C090404F7F0FF04060F0B0806FDFDF6FBFBF2FFF4FAF80CF9FFFAFFFE000703060BF900F80A01FEFFFF0201FA00FE0BEC03F7FA00F8F60C040300F6FC03FC000014FD09F80203F7FC0A0D0007F806EFFF0409FF0007FF06ED0A0A02F3F1EF0403F8070405EF0102EA06FA0206000308FA0205010FFFFCF6EF0C16FAF7FA010DFD0503150EF903060803040004FD01FE010607FC15020808020406FDF5F304F7F8F6060AF7F7030CFC01050404FB01F8F8080905F70607060AF3F303F50501031200F502001205FE01030503FF0400F2F2080AF7F9F8020DFFF5FF03F8070806FB06020704EF02FD0C0108F90808FF0109F70BFC0BFFF30801130A09080202040EFFF504F5010704050A01EDFB0DFB1109FA0605FB030CFD01FC1601FAF9FD09FF0CFFFDFCFCF5F8F50606F9F8060102FBFFFBFB080CF913FB0409F9F90C0EFCF8F9FDFEF602F4FCFDFC040902050AF8FCF9FEF40C0A04100701F60E0901FC0200FF08FAFFFA05F70803FC0E0707F9010A0600FB010CFA06FD010EFEF7F7030011F202FFF5FFF20706F7FD03F50205F70500FAFFF705EEF7080C0A03020C0001000702FE02000404FC0105F0F7FFF0F7F6FB020DFEFBFDF0FF07FD090202FE080201000B0409070AF807020EF402FE1003FE02061202F8FF02F200EBF90BF8FDFBFF04000704FF0306F7F3FA04FD04FE0CFDF80BF804020501F3000403FAF8F704FEF80FFEFF06FCFA0109FE0AF200FC05F3FEF90701F40E00F1F8FD020BFA0B09ECF2FC090800FD05F109FAF708EB0C0D01F30AFD0B06F70004F9FF0902F604FAFC02FFFB02F5F50900FFF811FCFFFF09041402F5F604FC07FE01F4FD02F0FAFC0005FCF8FD06F80003FDF9FD0EF7FDF907F5FF07030B03FDFE00FE000F000A0D0303031508FAFA07FD0314FF0CFA0B0206FAFCFA02FE01F7FB03F70004F7F409FB04FFFE0601FF0AFA05F90401F51005020A08FF0A0CF805F5FF02FF0501F503F8FCFD000403F5FDF1F805EDFEF9FD0B04000008FF07FB0C14F8F9FB08F8FFFA05EFFBFF0002FDFAF2F802F8FB1A0202FC0010FBFBFEF9F705FDFCFA07FE14FA010D070B0105000008FCEF04F6FD0B04F9FFF00DFC0A050608F9FAFAFE00F7030BF50BFD10F5FA0F0100080C10FD0600FBF30A0A0CF4FF01F20A0E0D0CF4150000FC00FC00FF0E07FCFAF90CFDFDFCFDFCFEFCFEEE0A03FC07F80205F902F60A0208F3F7FEF90202FD00030004F2FAF903FBF80103060FFFF9EFFF01F412EC0A0105F901FF0E0A03080202F6FF09FBF209FA0DE805F4FBF506060110FC00050FF5EFF50A0BFEFF04FE14F3FF020EF802020508F5F9FEF90806FCFD050E0711070D01FA060205FF0204EFFEFDFB07FEFD00F90B030508F6EF02FE16F400FDFB060304F701F3F4F90501FB03F8F404E5FF02FB0A00F8FE0C0B000AFBFCFBFF00FAF2F5FFEB05F0FA05F5FFFA0408FB00F90204FF01FEF00C12FF01F8F6070402000502FCFC0B04010E021103FF0BEF09EAFDFB06FDF9FA080CFF11F90AFCFAFFFE0301030001EE0EF3FF0000F40FFEFB08EA00000F08FBFD10F307F1F5F200050105F9FB0AFD031302F4FFEEFE00F709080BF7FD06FE0EF5EB010DFD0607F707FA08EE08FB0007FF08010102FA00FB00000803FD04F806020500F8060CF4F9060EF9FE020A05FB0CFC0006070202F6F20003FD0DF106FA0206F10304FDF80F00FDF6EEFFFDFE0FEBFC0D1BFB05FDFF02FA0806F7FCF1FAFD0403FF06F80E01FBFBFC0C02F203F90B0B02FA0CF815FFFE080201FB16FA02040802F50503FDFFF8F7FBFD020202F8060D0601FC04F1030202F30FFEFCF50BF60901F306FE0605F9F8F502020A0406FAF80208FDF8F9FCFFFD0EF30B04FFF5FD040001F8040105010100FE0E00FDFB0606FA04F505F9F811040607FD080500FA07F300FFFBFEF0F5FD0209F802FF04FFF9FCF9FF0AFBF800F80AF9FC09F9F6FB0EF80805130B06FFFF0104F40603F10001FAFD06EDE5F8F90C05FF1202FEF000EA0006F801FFFAF1130CFDF9FCF806F913FFFE120609FAFB06000CFBF804F40BF4FCEC00FEFD01FB0307111206F9F1FF06FE020CFDF802FC0003F9FEFE06FEFBFBFB0BF8FB0C04030BFB00FC0405F8FD16FBFF0207EA08FEF5FA0F07F50506FE00F3F20BFC0A07F702080FF509060203FD0505FD0BF6F905021206FAFDFF03FFFC01F20204FEF6010009FF0BFDFC0C120402FF0602FC0106F40601FDF6010B0611FFFFFFFFF8F5F7FEFCF5FB02F805F50105FEFF0202FF09EC0408FC03EDFA00F306FCF8FD0301FBFA08F8FD0007FCFFF6061011F80E090D07050506FAF00AF8FFF40304FE020AFCFF0CFFFB0BFD0E0C05F50C02F60EFEFDEDFF090802FE09000BFF06F208FAF9FF07F500050901F508F1EC0BF9F6F30FF8F006F6F50007F107EBFFF80406FA090401FBF812F608F4FB09FB0FF8F0F1EEFBFE050011FF0AF7FDF201070C050EFEF5EFFD01FF08F500F3090BFCF201FEF90BFA0DEE01FDF8F5040109FA03FE0601F30D02F904FEF804FE070700FB12FAFDF9FA13030A02FAFFF60407FA02FCFEF3010E04FC04FAFCF4F7F802090307070F03F808F911F4FBF004FCF704FB0B13F8030B01F307FBF405FFF30105FAFC07040605FB0006F7031001FE120600FB02FD08FFF60C0709050108FC0CEEF41AF40DFD02FAF50AF6FEF10000F2FEFDF0F6FBF9F30BEE0D0E0105F6FF01F105070E06FEFF0713FBEBF8FF06F50100F504FDFBFD080AF9F6EDF9EBF60B09FE0C00FDFDFE00F51EF9050E0A030101F70A01FF02071BF2FEFCFF040309FA03FBFEFC14FCFDF6FC01FB0AFF020206F6FA0D11F7090BF9FCFD07FC080709FE06F4FDF5FDFBFF01FA0F0607FC02080601FF0506F7010808030006FCF8080F0200F2EF0AFF03EDFB03EEFA02F00AFFF80DFA11FDFA0FF801010BEE0103FA02F5FE03010704FCF6FEFF07FC0901FEFFFDFAFDFCF002EF02080506091208FA0B0AFB0116FD00FE0416FE01F70B0DF60110FF0608F60507F701F6FF06F3FFFE0BFCFCFBFBF80204FDFF11F307010301F6FAF003FE050704FD02FFFAF503000103F0FDFF1006060EF1FAFE00F8FE0A08FC02F8FA051406F508F8080306FEF8FE0F0108001305030DEE07060608FF07FE1107FDF80105F20A10FA03010C02FAFEEDF3FBFCFE060104FCFF01FF06F6F5FDF4FD0904F702FB0EF6FEFE01F8FAFB0003FBFB01F915F407FDFDFA01F9FC0A000807F5F8F4FDEFFE09050B0BFCF9FA030103F2FDF907FEFAF70502000FF50605FCFDFE0A05FDF5000201FE0EFAF613F9FB000404EF00FE0D04F901FBF60CF7FFFD0203F8FDF70301F2EB020405FFFD03FB09020405F5FD06FD10FD06F5F5F808090404FFFDFE08F9EFFF05040CF5F412FF03F4EE0308001007EF0BEE05FAF80FFA02080807F90001F8020906F4F2FAF901F501060800FC0306081601FAFD07010EF606FD05F9F305FDF504FDFF060F0606110A0002FA030007F80E040BFFFF070100F6050008FB05F8EEF70005FC0409FE0007FFFA1000FE08FD030B0104030D0106F8F9F4FA0901F8F7FEFE000D0FEB08FF020B0605010B010EFDF500F6FB02F6F8070000030AF6FC0702FAF90DFDF80009F901060B07FEFDF002F206F109000DFDF1FCFCFBEF07F7F307F608FD00F803F809F9010408FCFFF807FD00FC0DFDEDF90706FFF7FF0DFA02FA00F60000100205050B00F602FFFBF70BF60001000A09060A0A0A05F7FD01EE0EF8020BF5F006070AFD040B01F2040EF0F60AF90BFC03F9F102FDF50000F3FB090FFC0705F209090304FFFA07FDFA02060507F705EFFF0BFA01F2FBFDF503FEF5050E01F908F406030005FDFC1603F601FE00FE03F407F50308FEFF0403F90204F509F1FDFD0503FB05090802070D07F9F8EE050E0107FB00FB03FA07FBEE0012F310E4FF04FE000C01FDFF0206FBFDFEFD0802F6FC030702FB0203F7FE00FAF6F8ECF5010DFE00040BF3FF05FD0807F2FFF9FEF9FB08F7F7F6FBFE010708EEFD04FEFFED0209F703EF07FB0AEC020BFF060AFC040308FE030D05F109120CF80001F4F708FDFDFCF80B00F203F8F6040203020010030BF9FAF9FFF404F6F9FBFDF6FEF8F7FC01F2FCF8F3FFFFFCF7F20B0F0A00FBFCFD0408F50DFEFF08F504ECFD13FD10FD08FD05FF06F70407000C1005FAF7FBFBF304050DEC0101FF0CF1060010FDF9FF0705F404EF0307F9000106F801FB06F802F8FF080C0CECFE07F5F4FD06FCFC0401F7FE050EF3F9FE03FCF10302FF08040907000104F90303FE11F80602F5F70201FDFA060FFD03F40A1205F205070AFDFC0A0002FE01F902FFECF9000D00060EF0FB0D06EBF50C02F614F4080402FB000704FBF706020814FF0A01FC0A0E00F0F8F607F8F608F1050C04060213030BF60606F9040C03FBFFF3120403FDE70003F4010EEB090001F40903F8F9F5EF06110D08FD03FF0408FDF50D0407F9070201F9FEF4140F04000A07FA08FEE907030A05F6F8F4F7FC09F502FAF808F6F6FF06FA0100FF0B0D03ECF50AF7F0FAFD0A040901FD0FEFF70BFDF104F600FDF5F1F80DED0D070009F907EDEDEFF201F90B03F90D0301F5F706F402F5F7FFFF0AFBFFFAFF08F9FEF5120405FEF808FF05010A0AF9FD0DFDFAFAFBFD01EAF6FE0BF3F7080306F60D000403FEF90AFB03F70D06F907FDF811FDF806F707ED03FE0900F508FF0C07F704130805F6FA0300FCF900F804FCFB010309FAF9FE04F505F501FEFB00F4FA0A0500FF020A03F90EFFF80305F8FCFC03F20408F5FE00FA0009FAF80EFD0A0311FBFF0D00F9060505F60504FFFEFEFE0AEE0A010EFCF701FFF80302F0F9FCFDF5011209F8F80500F4EC090CFD0601F612FEFB0F0201FCF31007F7FCFFFD0F09F01013010707FB020307020402F9FF00FB00000E050600F7FAFB02FAFE0CFFEE050E020D07010304FFFCF9FB1101060907F606FCFAF9FFF90408FAFE080DF8F7040C07EFFCF7FBFE0CFCF40DF908F0FFF80803FD13FE000208FC0EF6040AF1FD01F2FDFF0800F80FFD0FF807FFF9FB01FAF8F9FCF8F7030CF7FC0305FE04010003080AFC0300F5FE04060AFAF114FFFDF5FFFFF70AF7170416FE0AF70207FB0AF5FDFA06F3FC10FD01FB040A03F9030707F708070507F308FB0AFA04020404F8EBFA06FCF403FDF7F7FC0D070306F5FA000AF0FDF300010EF8F9FEFEF904F801FBFDFEFE04F2F0FAF8F2F005020FFEF7F9F4040AFF000BFB03FD02F9FDFCF6FD11F815F701F2FF000CF7FF000AFB0F06FF011400FD04FE050209FC03FAF807FBF8FD060201FD07F607FF010E110AF2FBFBFD17F11508FC0202FA06F90B0A0602EF02FCF0F203F4F8FFFDEEFBFC0512FF01FB0103FFFCF5FCFB00F703FC0A09FD02F8FDF4FA07FFF404FBFB0CFDFFF1030610F3F6F5010017F501F505F4F603F1FF0A04060209FC010608EEFF05F60604FAF90202FB030502F4FD05F70312FAFF0007FCFB0B020C07090AF9F2F6F8F203F1FBFF000207030411FA01FFFE0303EFFE0606FEF6FD000901F900FD07FD0B00FC050004070DFD050CF50B0D03000104FC0002FCF6FCFFF008050D0002F00CFC0506FA030DF604EEFDF90609E902F5060AF7FA0A0309F90BFE02010E0B0405FFF6FAF80307FD09FFFDFFEE06E8FF090805F5FDF6F90C07F40C04FB0BFA04FBE604040DFFFEEF05ED020108FA00F804FEF00304FBFDF702FB02FAFA06E208040402F2F4080501FDFA00070DFAEF11030F10FDFB08FE0B08090101F1FEF7F5FF02FDF8FA02F8FEF8FAFDF4F5FF0809010108F80106FC0701FA0C0B0DFD050301EFEFF7FE04070A050C0BF6F308FC06090207FF0AF4FF09000A020304F5FE0204FC01000101E9FBFFFF090303F5EB1802011204090EF80004F9FEFFECF2FCFCF303F8F505FF07F80700040D08FE08F00006040006F9FAF7000C09F804FFFC00F910EAF8F80700FCF5F6FF0213F8FEFAF8150208FBF80803EE06FAF504EEF50F0800FAFCFFFA02030211F50CF6F8F004FBFC01FD0BF20CF7F60100F7FA05FFEE0507030807F8FCF6FB01FA010600FC05010308F61402F605FCF9FFF40601FCF9F605FF05FE0011F80AF9FC03FD00010B11F8F90802F9FC0600F50A02120501FA11FD03010AFC030C040405FB02FB04F0FAFCFBF700FC10FCF6FC01F301FCF803090DFA18FB0701030DFD02F204080806F106FEE9FDF507FF1505F8F8F8010C030909FD050306F40F08FE120A0C050102F7FBFB000604F8FDF8FEF8FE080AFDFD08FF00F007FC0003000401FDFFFAFC030401F2F6020CFDF80401F7F807FB07020C000C0101FEF6FEFDF60811140E08011407F905FA04F504F60AEDFB0210EFE405FBFFF9F0FB04F9F311F7FAF104F0F6000009F8F5F8F7F303FD0201041A04FC0509050301FE03F109F1F7F9F10311FFF3FB01F8F9F6F6F8F80AFBFDF20AFCFF0CFF01FBF8F4080DF502F201F0F7FC0AEC0713F6F90206F7FEF502FD05F9EB110001F8020706FA02F80105F404FD13F8F5FAF104FB0E0301FCFD04FBFF02F5FDEF03FEFBFD010D0708FFF4F6FEFFFEFD0CFEFD01F8FA1207090001050AF20909F50513FA06F3FBF4F7F40301010307FBF60103FB0B0AEDFC0D08FE09060A03120CFCFC1800F5FA030AF901F2E8F903F201F7050DF8F9FDED06FEF8F0F5060507F5031001FD05F3F008FE0BF9FE03F5000600FA0103FD06F8FDF9090CFDF60CFE050A05FFF8F206FD0904FC0200000106EF07F301FD08F701EDFD0300070F1404F30BF506F4FC09FAFFECF8F7FCFDFAFB07F60AFD0AFD03EE090900F80207F0FE04FE060A0B0603FDF9FC0000FBF3080B07F90904F0F808FC00F9FEFA04FFFE05FDF3F206F8FDFB03FB07FA010CF60DF7010FFEFFF40407F302FEF0F80D011306FE06F905F312FCF10107F3060310FFFDF507FF02F3FEF1FBF700FB060305050803F90703FA0405020BFC02FE03F315FF12F80806F80201FA020E00F9F9F90309FF080D0501FBF512050201FD08010AFBF606F00908FD09FBF30810FBF7FD130109FCEDFBFEF901FC01FC0FFC0403F51A1603F20812EB0404F9FDF700FB08F5EDFF01F108FE030CFCFDE9FF040703FC061100020C04FDEDF9F8FC0A0603FC02FE0AF806FA04050AFB0AF2F4F6FBF4090E060504FF0703FB010401040606F9010C00FC0B080400FBFD0B0C0505FBF3FCFCFEF610040D0808FEF4F001FB000EFF070806FFFCFEF406F9F4F8EFFFFC0605F80805EB0403F91313FF0303FC010206FE06F4F7FF000AFDFBF707FAFE04EE090A08EE0AF8F9FCF7F50C0308020C02F3FB030701FF0D08050BFBFCF9FE1200030704FFFCF9F3F00CF7FDF805F501050302F7F9F3F8F608FEF105FE05EFF6F7FBFF05FE0713FCF80D0B0303FC05020B13FEFEF9F1FD15FFFE01FAF508FAF703FDF81208FFF9F90700F4FDFFFEF502FDF90B0C05070701FE0AFAF9FD0706100007FEFA0DFEFC0BF8FDF6FD0104FB0309F706FC0606F101F9FEFCFB1302FCF4FFFCFA03FCFF07FFFD1509EF13030001EE040105F901F908EF09F7FE05FEF1F60CFEF7F50704EE05F801F70208FD01FEF812FEFAF50102F4EE0102F10906F902FA1316F3F60406EDFAFBF7F5F808FD04F807060701FF09FAF4F604F60002EEFCF2FC13FEFA02FDFA0814030207F6150004F6F501F10708FA0FF7F9FA0108F2F705020101F30E0906F50F0BFAF7EF0907FA0003F4F8040A08FC03FFFAF4F8060605FCF807FB0DFFF6FDFBF7FEF8060F05FFF700F1090907F403EC03F705FB10030E0AFAF7F7FD030AF6F205F603F3FB0AFAFF07F40BFF03FC08FE04FAFA0001F60903F602060DF6FBFF0801EF02F7FE000E0BFEFAFDF506F503FAEAFDF5FEF50C09FFFD00F9F6F809F9FA090102F5FF0000050A0400FD0DFD0705ECEFFFF7030200F2EBFAFA02040E0CFCFBFD00F90600FBEF0609F0FE03FCF9FDFC05FB08F3FB130DFF0F0208FB1006FDFA02F902FB1108FDFFF8FEFD0311FFFA0502FBF905FD0AFEF50C08FC06F1FBF7F40003FD07FF060809FBFEF30A0500F6080BF60D0CFC030707F5F413F903FE040006040A0C0403F612090002050E0AF610FB0401FDECFBF7020DF9030405F609F0F0ED0103050FFD0B070506F808FBF6FB0519020703F808000609F402FF0907F3F9FC0704F7FCF7FBFB0D070AFA0BFCFC130AF804F2FF040105F0F1020408FDFCF9F407F7FBFDF300FBFC050A0E12F4031504FAFD0214FDF103F2FEFC0801FEFEFEF91502F10700FF08FCFB01FD060C01F6FC01FD070107FFEEF010FD0B0208FA00F7040103FF0BF904FEFF02FC07FEFE0007F80305030204FA03F9E8000D02FFF4F4F60408040105F2FC0DFD0504110102FE0F00E706F301F7FF0108F90305FA0202F3FCF612F101FEFBF80E0C09FDFD000013FEF6FEF6FE0BFF03170001EC0501FBF4FDF0F80BFEFD111C02FFFC06F801F8FDF9010BFE09F6050602020BFB0C05F90401ED0FF6060E02E9FFFFF700FE0E0304F2F5F6FBFAFCFD03FCF5040417FDF506FB05000BF8FFF1FE0405F102FEFCFE08040C0F06ECF6F6F9F4FFF9F9FBF8F1FE00FFF40CF4FD07FB0503010B15F3FEF90CEEF80AFF0BFE08FFFAF3FB0AF301FEFDFD01020706FEF5F714F601FD040A0A0D0A0BFF07FA0700EFFDFDFBFC06FDFE00F804F3FF00FF0AFAF9F801050F07FC0310F7FA08FAFF0203FEF6EEEBFF000009F906FA0A0CEEFFF1F7F4FEF3F5F4FFFC10FB080608FFF6F906FCF6FE0D00FF040B02FC0BFF00060D01060A0AF600F50CFFFCFBFFFBF9F8FE070EF5FEF8FBF606FB000DF706EE04FBF0030BF000F3F8FDF002FA070A0603FB02FC01FEFA0407FA00F2FFFDF2F80BF607F70402FC07F211FDF8FF0101FDEDF601F4FBEB04F0FB04FCFD050101080A0AF50302FC0803FE1200FEFDFDF3EE090E050DF9F803EEF1F4F802FCFD11FB02F4FE02040A0C03FF1506F806FEF9FCFE01FCF4FD0104FA02FCF9EC0911FAF2FD06FD03030A05F50A04F4FEF7FF0B050001FB07F80601FEFE0CF9F7EF0C1209FE06FF07FEFA0600FF02080207FFFAF5F407F5FAF500F90303F81106F90BFC050FF900000D07FD02F9090306F90107F407F3F7000007FDFC090003FEFA00F307FDEEF0F9FFF70CFF0BFAF9EB0B040605F70504FA00020CFEFF09FD0CFD03FB00FF0CFE0205FC02FB08FE06FCFF06FD070108FC0DF60E070EF7040D00FF00F1FE0108FBFD090008F7F812F006F7F2F60916F0FD0F02030404010502FEF6F0FBF10BFDFCF3FD0F0FFE08FF00FD02FD0602FE00040CF100010AFCFFF90818F800FC010F0AF60A0403FAFBFF030803080DFDF9FEFC070E0AF30200FBFCFBF913FBF3F9F9F1F7EB0C06FCFF040D01F0FF080009FA0C0AFF0505040008FDF6FE01FDF8FAFD01F9F90708010908FEFAFEFC010C000312040804F6F8FD0406020D0004000405F8F8070EF8F4FC070706F80A05F4F90803FEF3FC00F6F206FA0CFAEB00FC00000AF2FBF705FF0AFFFF0604FF040709040401FE1505F1000EFA030F0BFFFE0602FD03FEF5FF03FBF0000F07FB030000EF01FDFA09F80A0606F804F5EB10F502080606FE0A0703FFFC0308F40806FEFFF9F6FDFBFFF1FD090501FAFB050E0BF9FEF8FFFE00F701FFFAFE04FF0204FE0CFCFF00FCEB0F0D0006070606FB0802FB07FB060306F907EFFFFFF8FA0AFE02FEF70F06F8010100041211020608F505030407F400FEEE09F9F3040D0B00F703FDFD01FA0004050E01FCF801F6010806060308FC0AFE070BF813F9FE0F0EF8F700F708FBFCF2FDFC0DFEF4F7F70BF9F4FEFCFCFC0BFFFE0AFFFFF6FDFAFEF7FD0C1005040AFBFC10F9F907FE0AFB090808050100FCF901FB190FF20C00F8090106000C050AFC0A0201FA0902FE0503F2FAFF06F0FAF6FB050204F905F604F6FCF90005FDFB00FC0B05F1FDFEFB00FEF703F705F8080A12FCFEFA08120001ECFEF7F40FFC05060008FBFFF607FAFDF403FAF616F8F402F9ED1204090A05EB00EEFC10FD03F602FA0FFD010807060504FCFFFFFE0C0BFEF7F7F4010C0AFDFA040EFE0415F702F3F3F905FC0A0102010B05F4FFF7FBFBF701F80EF400FA03F8FEFC08F9F91BFC05FEECF90B06F907F9FBF409FA02F70605F5F6FC09F5FC0BFFFEF007FC0B080001FAEFFA0DFBFEFD0504FD0CFA0205F6F908ECEEF8EFFFFA02FC03020BFFFAFA0E03FE08F400EF0102F8F1FD0002FDEB060AF9060808060712030401F603F5EB01F6F604FBF8F0FDF201F50AFD000606FB03FF010E01F9F707FD000C04FD020F0600020AF403FE0707F902FA0CF8000503FFFA09041019FF0CFDF8F0F5FCEC0BF7FEF80610FF04000504F5F907F204ED0009F70002FF03010208FE08070404F606FE11F4020C0505FD05FDFF00FAFDFD03FDF10A0103FCF801FEF4FD0604FAFDF2020004FB0A021DF90308FB03030109F008FF01FF05090007F107FFF70607070600FBF602FC09FEEE00F7ECF201FCFB00FE0000FA0803FAFCF8FB03FAF8FAFC0B0903FF0906041003FBF901F900FE05FCFEF5FB020203FB00FFFB050B0503050003FEF8FB050105F9FE00F9070802FC0BF6FC0FEF08F406FE0313070404FFF8FBF9FA090AFBF9FD01F4020504FE020FF8F3FA00080308000C0FF6FD010BE603F305FBFFFB0111FCF5FC09F5FC0D111200FD0306FF1101030608FF04FF07FFF6F40301F303FEFD0903FE13F201FF03FFF5F703FD0A10FAFB0105FDFD0D040FFEFEF8F60CFA10F2FE00F404F7000DF304FCFCF40F0209FB020300F7F1FDFEF5FCF8F9F7FE0DFA04F20D02FF0B0EF80AFB0AF90A01FBF303FDF2FAF706F802F806F60209FB010702F5F502F9FAFCFCFEF9FCFDFAF80108FDF609FB00F6F1F7F607FB0C0101FB0BF00BFFF71A0104F004FEF3FD01F90DFB04F507010B09FDFBFC18FFFAFC040EFE09040004060708F8FDFFFD01050703FDEDFA08F5F4F0F2090811030701140AF9FC03050306FF0A0D0109F5F1FF0107FC0200030DF801F5ED0613FFFEEE0C0A030705FB0A0E00F8F5FC0A0C02FCFEF9070F0208030E01EFFB08F5090A05F8FEF7FDFD1001FEFB0209F70F0202FA02F900FCFE0208FCFFF4FAEBF7F6FE010801F606EB04FC0BFC04FC00F8F805F10FFA050DF30407FD04F0FBFA01F602F8FD04FD060EFC080F070F0014FB050F0106FEF00503F4F5FEFAF600F0FDFA0FF604FA0F09020010EFF5FEF904FC090904FE050900FFFA05FE0BFB00F9FAF4FDF4FEFA0CFC0D040701060204080E06040402FAFB00000006FE04F0FB1200FCFDFBFE020C0A0BFA010C0CF2FB030404F90D0208FD0900F7F9FC030CF3EE0416FE08FF0DFEF0F3F4070DF904F907EF07FFFA04060406F0000D00F3070406F6F5F808F4F8FB0B05F90EFD0705FEF0F9FB0803090C02FBFEF6FCF1F3F9020405FD07F30EDE000108FB06FCFEFEFDF3FDFBF4FC0302010204F2FBF50309FFFCFFF500FE0C0A0A0E05E9FEF90609F80405EEF8FD020303FE0AFE16FEFFFDFEF6FB07ECF308FFFF07FB0A03F6FD0004080FF902FEFEF70A09030A0910F80E000E11060DF505FDFF0402090C0FF708F6F2FDF505F5010706F90606040704FDFAFF00FBF7FAFF0D0A0110010201FAFAFB1706FE0203FD03F6F5FCF8F20D021205FD050BF6F1F10205040BFEF3FCFBFEF9FEF1FD0500F9F9000106EF08000908F4F8F0F4F8F90602FF0601F3ECFD04F6FE0C010202F4030001F900080704FB030108F9FDFEF8030602FE04070BF8F50713F8FAF102FB0DE6FC07FB04F707FC0E01EFF80B00FC05FAFEFDFCFDF703FF0706F8EE0308FAFDF501FF02FCFDFFF2F5020B0B10010D000A0A01FEFDFD02F60205FAFFF109FA0804FB040F02050012F603FEFC06000FFF01F8000A0613FD0BFAF6FDF806ECFD09F20000F2F8FFFEFD14FFFCFDFE16050C03FC030D0811FD16FE12FFFF140CFEF2FA0806F205FD08EBF2020905F9F2F9090601060300FFF9FA14FA0B05F4FEFD08FE0D0A0003FE07F7FBF8F7FCF502F70204110C1101F601FDF1080D02FF06000403FAF90701040AF0FB0103F5F903F4F0F510F5FEEF0A0107F505FF0401FCFD05FD030B060FF9F602FCF8EBFE06010DF70DFDFA06FDFDFDFA04FD00FB08FAFF090108FB05F00C0BF700FB04FD05F6FEF2F80608FC03FEF7F7020B050FF60EEF0600F7F50A0C0305F7F6FBF3FD06FB06FAFF0012FB0602FF0705F700FB06FF09FD040206F202F401FF07030105F8F305000E04FFF7F8F804FA04F70006FA0BFEFBF8FDFFEF08FBFE02ED03020BF506F7FC03070400020E0608FE04E9FB0106FCFEECF902FE041700F6F605F6FF05FE00030404000D0500F8050200FBEF0AFF01FEFCF40604FEFD0A080008EFFAF7FC0607F9FE0106060108F4F7F3F004FAEDF7FD06F8EC03FD030E031108090A090301FEF60603FDF501060A06F9F9FBFDEE0D0805040902F403FBF901F8FA0602F602FAFEFC02050C0902030A030D0D08F30609F80CF70507FBF6FD08FEF7FEF909F6F90808F8FEF904000BF809FC01FC0C0DFB0AF20404F912FFF501F60A05F2030B0614FF030000FC0008F60C05F200FA070104FE0FFB090C0E0806F0FEFEF8010000FA0305090507F6FFFAFD0D020BF80511FB01160A03FA04F309F9FB0D0D0302FFFAFE05FEF901FD09F51201FF0F05130503F8F7FD02FAFAF903070D0BFD0D0C0605EB07060909FE0D0CF500FCFEFD1306010502FBF4FCFA100DF0FEF9030009020009040901F607F8FBFFF70CFD04020708FEF506FCFEFEFAF702FDF705030902F9FA01FF0110FCFB0702F9FA0BF70C0A02020B0705FB0000FB11FFF2F80BFEFA14F7F6030108080001F111030715FDED0A0B05FAF5F7FBF605FCFD0401F3FEE7F20007060E020503090608060306FA0DF4F202F4F2FD05010B0005F7F7FE05FBF50BFFFAF80707F706EDF8FDFEFAF3FF010905FE0B000909FB0AFFF801FA0D0C05F7E1090200030B00090BFA06FAFBFA0A03051201FB0600F4010F03F80B0AF1FCFFFEF101F704F4F201030214FBE0FCFFFE0CFEF2F5FB0B02FBF8FD02F5F2FA0405FEF60BF705FD00F909FEFFF301000902F510F000010001FBFBFDF0FFFD0E05FFFAF1F91304FE04FF0201FD0A06090305FA03F0FE01F6F802FBFFFFFB01F4FB0AEB080308F9F201FFF403FC0C0AFDEF0F010203EDF3FF09030606FFFCFE040101FB02FEF3FD06F6EF02F6FDFFFD0100FB12FDFEFFF604020F07050B0F1AFD0406F7FAFEF2F70F0A090BF108FFFA0402F2FEF7FDF9F5F500F9F5FF0F06F50A0204FBF6F70A06F707FD06FA03080306FDFB0905FEFA08F00B0FFF11010403040600FFFA02070202F30A03FBF1040C0F0008FBF904F70E02060A08FDFEF5F3F6FBF80807090AF6FBF7F601FCF41700F806FA0201FBFC0B0BFBFC08F1F401FAFE0D0406000407FA090505090BFD0B0507F4FD0A09F90408090AF8FCF40CFA0B0218FBFD0411FEFE1004FDF201F80213000509FF110CFA01FA0201F602060811F30900FEFBFFFCF10106080A081107F1FF0C0303F506F6F703030AFCFBFA0EFD02F4020102E805F90501030FF4FA0409020109F2080BFE01F91302FCF61803000CF4050BFBF5FA11FE03FC031402F8F0F802F9F6FA0D1508060605070AF406FDFBFA000C0A04F90003FBFA0E0007FF0000060600FCFF01FD0009FDFFFDFFF50D00FCF5F5F3061308F1FEFD060903FF03FCF7F2FEFF06010904F214F703FE0104000B0405FA06FBFDFE03F50202110EFEEF041205EA070607FB00F9FE03F8F7FCF60F0512FCF70200FDF804FD0702FF030804160803FD0C0906FE03F7FF070E0219F5FAFF05F20401F904FDF2FA050C00FDF102F4F60BF90CF307F9F6FCF5010BFA0601FFEE07FBFEFE15FEFA0CFFF5010DFE06F60700F6FB05F708ED1201FCEB0AFD01ECF7FDF5FC010300FE02FDFEF7F50803F4FC05110400E90211020901F705F606F802FC09FF0B0AF9F9F7FEFFFB0CFFFDF309FCFD03FD0F0FFD09FB02010CF30001FD0103FEF1FF08F8FDF3100AF908EA07070201FD050C03FA0D1104FBF7FB08F20EF9FF01FCFF080313F70001F8FD0B05FB11F7FD0109FE000DFCF603F8F80A02F905FFF80CF9050607F80204FE100000FF020B04FDFB0109FB1001F7FFF8EBF4F408FD0F0BF9030EFCF4FCF600FE030807FD04F8F3F8FB0A08EE050304020606FF03F80AF006F1FFF806170305FCF507F704F80A10F7FC01F900F602FCF8FB0009F10704FA0404FE0FF70400F000FFF701FAFD09030203070901FCECF206090109000B0FFE0A040203F7FA0BFBFF02F800F9F8F9FCFC0C0504F9010FF9FF08030504FE050104F8F813FBFE0B00FFFA050603100505070003FF0D020FFE01FB05EC12FDFEF009F4F4050BF6FDF20802FAFC020C02ECFE010602FE01FBEFFFFE06FC100EFEF6F6070AF9FC05FB05F701040909FD03F1FAF400FB07F908000A03EF01031005F511F9FEFBFC06050812EBFCF90103060516F8FBFAFEF9FD020AFE0A0C01FEFEFF05F40303EB07FD0106F9F601051206FE0E00FD03FD0E050C05F3FCF400F110F804FEFC0902080807F80CFAEF0A0F01EFF40D0305FBFF02020B040000F50706FD04FBFCFDFAFEF604FEF905FB0B08F6000C0E0700FFFB040209FD04F5F202030309FF02030D0A060B0404F0140201F7FEFE06F3030F06FD06F2FC04EFF6FDFA060407FCF30508070703EB03F9F70A010C0C020EFDF8FEFEFD0B0C0BF0FFF90714FB01F90FF60AFEFAF407FC0BEF1205F001FF0508FBF0F5FCFFFD04FBF9FE00FA0A090E0CFAF7F3FF0200020905F8FF050CFFFCF8FBFE110200FB060AEEFE15FD0BFB09070FFEFC12EEF80D06FD0108FB0805FA02FA110A0607FFECFA00F904F3FF06FB0C08050A000AF212FFFEF5F50204120305F803F5010508FCF80202F90F02F81705000007FB07FB120300FF07F7FA01F9FD0A0212F8FAFCF7FBF40705FB0B070204FAFFFEFC04020D08F5F70C04100FF9FCFDFBF7FFF3000A0800ED02000A0304FFFB00F7F80A0706070115FFFA01FAFAFBFD01F8FBFD02020204010AFDF703FDF2F10801FBF8F9FCFDF7FEFB12FC06FD0AEBF6F600FA0BFBFE06FFFEF9F9F3FD0701F3FF080CED10F500FC01F4090E050407010903040807060404F8F5FB05FFF1090CF0080BFDF30903F9F804FBEC00F60201F50302F6FF07060807F4EC05F8FE060DF8E208F300FDFCFDFEEB08F4090104F50106040E0B0D031608FDFF0E09F80DFE05F603FD10F705F9FB0609FCFA02030C03F806F806060506030807FE0107060005FA0EF81108F6EF0C06FC04F707FC04F202F7F60B0307F3030306080EFF0B0BF8F9FF0106FF07060104FE0A00F00204F9FE100708FCFCFDF002F4FAF6FE02FF07F30B08FE0E0CFAFA09FEFE0508100CFB0C030F0306FDF9010FFA03F6FDFA08F605FA08FFFDF407FCFF0600F416FB07FA0206FAFA03FA0507F3070E03100BFA01F602000C090004FCF6F30FFAF2FAF804F8FD00FE06060601030307060002000105FD04F608FF0101F90AF6100900FA0500F601F70909FDF1070C09FC02F4FC0805090DF10BF9010F05FBFB130DF20106F104F202FF06FCF1130CF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BCF62CAA4F63293C7A8F894856E9F263EB9CA4A0648141B4B0EA3A2D3364C36A83");
-        int smlen = 6209;
-        byte[] sm = Hex.decode("3FB2759E1BCA07730566D4FBBF3FC19A39C68F190095789EC555884EE97E9ACDB09109C5A9CA96658603D63A5A23C9D8DB7D8C54D07738E958BDA1E631ADED4329AE16B183374E1C3976DF1EC94BC79B773E8569973EDA418AC903B534A83B8B2F7CF7856ECED63F2626530EBBE3D22A734B457F2B1F168225753A0F3B030251CE7F414740D707D10A65BA6F7464E0291749AB9CC694A81CF54AB8AA21F3687D197FD3AD1EB37EAB1A19A67B1C396BC480B2CA19D650FCD9335FF245863787275540D7D1F5FCB278C558AD6CF15C154073A846B51754D45A2439FC08382E93CB657E1CFD3B3DEE5394C5F81513041A356D2EB938CF196B1B4BF96186ED75697745A3304BFA7ED23F77DFEBEC60A2C2441EE6B695CD17E2166956CACA25B09DE505187CBA23F62D86D0871BD6B8FD2D116743110DA3386A7401DAEF39506C9091B5D32675660A7380C5E837FB7206604C1A4931B8D6D452C5F4095EC1B7EF0CD11710DCB6A18749B788079AB1902135B62280D928167559696A3A2EAEFADE345F91D8CE49EBB109CC926C28077AB5B1B9BA946D929C2BB969061829A6E58A0FA5B168F5A1A7722CDF9A23CD75946CFAD9D624F4DB6A47C78EDA05EDBFE164F6FCB4C32BED70FC7D04BEAD5392F26ACF330B99A9A4140B5ACD0F215A16CC463F6BC4B7BD5D81B51F98C586E58C68BE97ADA48CED599933F2CDD994B9B52B3D93C44D9ACA191FDC0FD894C8AFD58B05D73432D6A90991FA87089B68D7CD7D1B136F7E3BF0FD266F82D13B1E456FBB6E29DF3D330E2D9440AC0AB5805C524463EDCA6D27ACB13B1B4F0881FF3A5A19E2D512A84192FDEA959FB87A555610DD44497EF3AAE91D075EECF7945FC2B8FC531B53FCD236687D05457999E1B3510D92FA433205E4E42E5B474E2FE7EAFD88BCD3375DAEDA0FAE4D40A63E09F2C99CD9620279BD1C34A8AD5B014CE8898B17AB33C1BD311CBF3CA6EBE0C9217AE3D09D978C0A8A41FC558A8D31BF37FEF59D1F46575CCEEA07F500E1CD85CD00FC20B449892E9C463C60342F526CA6F25F6291DC72330B5DA6C24CBC0CC75D8146CCFBD1FA47D36250EE5113BD9A54846E40FCDE4CF33E38328B48BCA3953C99495AB14B5AE2ADAE893181B1AD49EFF63D3A432DAA67572006A4001179363EAAA1701FF939D69FAA27EBD7BF71DA1029C84B4980251ED9C19AE67591AC719D2038D39BBF95B29B217A95CE10DDADF65CCD90A6BE492CFE0345D5AB3BA26F44846E96E330E853D68132B6F5B1B8913C02997551E55EF4A2C48B0EED24060FD549087E85ABDFADFB35B13D9A44EC677818EC64CF1446EB9F012160E5998272993377D8B751F87F8F370D904642EE9AEAEECF498ABEFD14FDFF1DB90C41401FC4F6DF93429A944ED1F90D834D7C5FBE5651FE291488605872926ED853C0B23E958DF376C75AE1909BCE043EA6AC3B035ED0350C07DE7749F7181E2DB5C5A4CFF983634AC34A4D416CBECAB4BC40C96EE2AB20A513E865B792F2BFCCDE4F9107CB9887F3FA77F4FA9D625EB32FB856036768F6E89424345BEC0CE938D71C37DE7C0CC130957C15D06E764D819AEB5E05328C50BBF7B01EA9F2952D5861705FE66A8EF3F22295C408259901ADF5E675A9D72125467B18C75A0CA5C84BE77E8A678FCB1A98EFD90496527B509CEE1DF01B1C535273B109E5ED1F8C6012EB79C7F5F5B9412A4F7DE6FFDE45F10F79BF1135A370C953BEF71EECBA75718A56441049B8C306E371B02250B179ADC42B9DC0963C9D6F991F66AFAF05B7ADFE8FFA71E8FF3BF14B08550A8499F543A8FA45C4BEF6D4635EAF889652E4EC4E24B7343C48F32C61C661291747B6FF99057C797B7FE93A58FC219D44C3973E1C11973F8CC00A29EB43235E0B9DE326F134FA55CFEF1B7D1AA00FFFCD8A3F9AFA1127EDD97C5D20D642203F65D5C5151AE773B3683ECE96726CC61C13997CC8C8F291D77779686E33BC09EEA6B8739122D2B90B78022F368B01833D94ECB2091BB45A45AA7784603502B1C54657F8A7F6E122C7B46393A466933BD806FB4411288DCABEA5E2788AB0C088AC9A45C587C087CA9FD4C131969BC9F2025DB8FA4484CBEF0C6C7D887FC22599C28593DC8D586ED81D38D35249D7E01FF6DB898CEE78B6214EF2C215B76E55FB0A65CA3EBBB5586F4255AA2BD802C5E2B60A6051B5C4BFDB48C4520290F2FB1BD5A6D156CA375C1E700765F9923ACA126D41CDBA2F2DF339D143870CDC2964B20483154EB8E8D59651C17FA7898176073DD353B629D327BA130F2E56A9594D177A1EE82847BA8F8AFDC669F7BE160F1EA6C6EFB747D9BAB0AC58DCDC8D33B5F7BAABE5274DD7D4B5AA439C713615D4B255814A5D2AF44D5382264F3F9F48872A38D36D1F15C90FB768C9B690EC989FF28CC135D29330D84EA33EB173271DDF8F5DF885076251370F59AC0E4DEEF640D5C04ECE8778C1D8F839462C5504916CBF555167836076C88A848903B3779E007745F3CCC8282CC526306DD63492F0A004A99821D1BBC065A255F4FDCF426BF97CF9933DA6A79B577919F7605446A25CB549A3057785522EF5A56E3288D42D714163F16A36EBC6607779691C7A1E5C420E24A64299AA03930DE872575A18A0D004CE7CCBE85BE4227F2146E176F0B0689BF51988ACAC366444E2281B5A81FA505E01223818185BE05074AF0C94C4B99F1AE6CB4033487DB1EA46B3A59A1B840845570F468F6E8BE1CEC813148FC421A4A62BEC0D8CE9DBE73AC17D44FE36C93DDCAB402EDB129BAA93795AE54608169F58BD8D115EC6392DBC2BEF8BBFB820827E76B266F480D4E7229B4119C35AE0AE1F222DAD22DD9CB3DBC76D80722F74E6BA87C7918BF33811850F3C9300E0FC880B889CD3EF2C3E2D63B21AB7A05867B460D7B96050BA8F812B79B29851CE5EA92202BB553972EB1ED8CB82289987EF0B314D2E787DDE3CAD96238D7BD182CC404F8B19F3FD4AA55C75DFEAFBF6228A37B72CF57152B4CF7A65872ECAACA21FAC42849BD2D1A5812196AC6A52EB51709710AE1BE2345465DD98D42135BDF67C1A022A338F4E60686746B62B2624FCA52A46DBE515714A2C8E9CF76B14D2A7FA3A734D27657DE99C8A0A5883A1A9ACD6568A0F5926E93AF2017CCF8623C7363CB52390A93FD1143C575AD3B35EEC387321EC87B93C16FF30DC8848DAD229B0CC561F75F64D948A0CD82AC62DDE0AB9DD13973E9B6A19DEC429620A6BD52F0203820F3D2DB2369D121A093E64071E926919BB8B7BB0C3E58EDD9A69F5ED8A1C7210290BFADCE31284BB69C5F02B5E8D13F19E159FF334DFC5AC94798E1C87230424AD32FAC495299F357F74D914FBA9349F0D26491444C5A7B2F0388ED098A182122BA1C1A8FB751C52376E4385449B06F92C3005EF39BDB758AB542F7B3BA43A9D6D3B1B88977712E0A8F08C898AF5DEA1DD4E83BFB763DCDDFD34EF33E208F98AF8F90AC173ADDAC9E701B3F014560A1D029598D91C6A5715A4102F2162B8FA44A4B9F002E0E1C8D2C5906745708648A690B8CA89E6ECDF1BA155EF1D256305D94D790A856C79E39A22470824AD09A52F96D15CA3B0C09D19CE23C7880D774AC00D59C3009183163FC98FF74F99E03A550283A8D300E1C618678E2F8EC2D69B15E822571ED7A5F35D6FCBE5AEEDB56FFB6352D60434CCA2C5DBA90E82AD9298FB1369A06A1FE93098C86AAEDBF846F59A3390845B8EDD2FB8D08622AF52DFF43F1918EFCE00D5184BC19D4C782027248DCD875EC1B9BDBFDC7DE08A92E4838AE7871B897D9F5BD23256B9686CB001B5B2DBE7D79B2094E2B26C103C38E81F51A75DB8871A6AD28AB5AADF94BECF43D928B155FD4900A3F0F2332B992C0091B8B21D8609BFE9A1EA84313ACB83ADBC8478320BEBEDA9AEF0BC1A9B3FC4BCEEC226D13AE4A5163DEA4D019B148EF98285717AC45A935CB71B103FFFD9B6739CF1916AF5F3D61AC37CD9584DB4A2F9A24D38F61B83232DD33680DC30C32CFB9DBA27A6BA2BB8B2159F3EDA7F37402212755225CD99CC1A37A99F2413A4C57D1A73D9BA1DD02E310DAD355A14FD01A3EED8F73008607775AF0A5BE621EC16F2111A5148F977E9F79460450CC21AC5FCD8EB04BBA536385563D288BF592D3ED7BFA3A12CD9F914CD0C35770A4A928705D25E13AB4A05C437E009595B14381A5E215870EB468B06607F30FD88CACD1C11478BAB4372B9438AE4DB6C606941BCE98322E0FDCF59009DFC5E7A4C483B00DDF2386A236272DBA0D8064F27B7C1BE90DA8B592CEA17EB33ABD5A7E19DD69A805A6AEB6773D0E8CF6450C3C2FE55E0B7180CDD8863106D75431482BDF9C182977B91330B0F0ABF796D5348CD84FE0036E52860F7801ECD8EA9E69784994D425CD5ED09A63251835A233ECA4CA9B9AF6CF154AE4465EBF089F19D32024AA2C144DA004F84A7A6F22C3D71600245AFF5F1E5A1D9D5CF24FCFC0228A534E9E6447CE7DDEBD8DCCAB3BC0933DB333A39031EEA2EF6B81A9BB34A7F67554FDFCA8048FCD97E76CC082A93FC4F98D32910B895682D1FB3F5E97E463131F56364283ECF2AE029149B9CB73817AEECA497360C23ADCE8111D86E2C7005ADD2C8C0503A99DC91D30F9D20866C17E296649B161416C181F136608A05434074D567CDE728F7B31B60B97C04055698E108E6A2A65A77EDBFBB2F05AEABABF166B6DF25DC3A70062050C779CBBA550A62F021CD6222165082EC37858901D0A46C815579AA998EEBF4B3387B6BD71B0F16EF5B68CBC6A47E1A3DE015331953F3D3510385F1E048ECB84E7EF929142225DF16234EBD672B686D297E5A9168C6F97EF6C97501D4E086E9D73D1994D3C38C912691D8F0E00E0E2F0CD95B42F3F70194E3183BF9537188D1105D44839F7F3EDAB0E10232DAA9FC673D6071F3589095B6C392A766F0DC9D9B8DD563B1EB82076CBBB7C5636C516D3F200A3B43D384E79042E936E51C3D1A4D3F621CF738EE51D6D29F22D658E2370410E841A5CA7C3122AC26BF26730AEC0B23D8CDB46B6F3E0C4EF15410B15D83BCDF67BF2AD9EDBBC3890F70670FBFF86A4645A58F1EB5C49C8F6973CFEE2E1E38879F59D1740172A52744BCD839CACAC94BC3DBDA37A97A2FBDAD6AE0CCDF080B57CDE454092D763A1020E176515075DCC37148AEDAC1C7BA090B12DBA260F08F04C689661EB926716ADF5FB7159822E226BD3DE2EBEE1F98685CBD99CCFB09A6C9FBFBE377A122FDC01B9B7FD82F759129A6D0B0FCC9E8AD566A19FA04C12B95C4154E35215F5F11255493AFD5E66B73D4E0572F01454D1848A13BC0A9374BDC90994B0004D9D2EAC8FB506E7CD764A1B2A9016BEDDBFB987741FB68265E5B05CDFA46A4A7A1BC25ABFEF37118CE8118BCF92F32C433E5252D8DFC195D4CE5699A2CDC1612EAA171423E6299F86C2A976DB4C1E421E140BFEE8406DC649C57FE27A77CAF3481B8687DB07F9921790AB5D502CED457C3538E688F143A69175A535101E1DEE82C5E39A579DA178CE233105BA5275E27E79441548D9E0F70CBEC5C7E179D6FD8E167F90C3A79D964CC23CFE18D6BC300753961BAEF0EC6AEDDDCFB1760B6DB3D0B667B8E120CD3F867017507783F223106CA7753D94BC60E65538589FA87807F7AB8791C1957C9510A9FBB91D513DE0ED7166155E911A5622A6A49DCF1DE3DEA4072E5DA75583B6EFE781FBFE31F7AE50F4FB8897586B8078144CAE9842CEDA34EB9E0B996E3CA46F11BCCA46ECB81DFB191236046294BB0120ADFDA2EC0DBB7D4CC6353BE9E64FD668710B1CCD305349C0B7877566E3DDC4E21985FA6000229004DFEA9F39421476A5E33C9163CDAE4C7676B1097E48C70DCBD4B39CE4AAC3ACFE73CE9C0D826B8A61CCA0DD2340C860E57496A1915BF065AA6557E26A151603D31CA94410E6C3E6D5AC80FF6F71DAE371D020CEBFB58EC73017C8D2D6D6221598678D752DBBA95113CDF71848D2E05D22613451627456A96D15BD277EE04884F3F296B0DC5314EBA22356CE09D4C0BB7246419D8674E4D10EF64B3F057D95E70BF1086ADDB100453D81E19939BB18AAC1343FABE39E308284A8D6A38BE238B6E0503A504727AC20B48A2C26C130E7A9678BEF07A1828ADDAB90740526F99C11D3217E001172CDD33D6D6E949A8ECB963C0A5283189AE8C98B21933FF691931758E842977A4F3F337252105CF22AD836F91E4FE5CE97FE4CE59EBCC4DA2A56D9413BD356BF65EF6FB2CA2201EB31BA56D717D2F9C038BFA76A52610AEA57D45500A1408AA8876AFF585A2FF309C3EC852B9568AE1CF9D97635759932E61D61DD6E67402912EC538BD0DF9D0BF25C862C91E301AC499E64C061FC14E89EC361A10FA0C85132BCEE7AF028C663BE3B0E12EAC52F691FB66E58E34EF2AF4C5A9DB0A0AB01DA372E39BB824F9D6AF847DF6A08F680B4F99466D6E6F404F0B92210D641375ECBC0C10A38DD1CF449A75BF2420A294EECD649013E160D793A247D1649E03A535292ED3D88EF7901DC845CD9703BF0948DB61C08EA89C33A3DA436874AD22ABEC43A5F2896833E15B3102A66E2A7D69D272FFFD4CC8979B9733718B95B605166DBE16ED1FD1C5CB2571E7D9FEAB2BAF693448C1E9613E8FBB25E05E2838390F3D9D2CA88D4B1739AEF4039A168E908D245ED38CA96F98569FFEA949946B76BF6F1DCA0B866CF0AE157314A28B76654EFECBABBA1C58ACB48E61587B42DE0F5BF8D5A3DB6C2E1D1632894D940CF10F71390B1DE07A4DEC38EA93A67439361E71FD8149D26DD8A21E9B98DA1FCC014052D0FF54DA3B2A335D0122FAA1332B193043A9119BAB7BC8F3209812B5280625C44ADBCB92907B6C387E0BDB1B9EDC0377C03E305C7333897588C2EFDDDC82E95ACAA329839EAE514F8E8A46A9126847933E73820922CF62528DB950EAF940F72A7383A486F5E210120987173A75A7D1B12097287F9146735A7BA110E22BE8B2FE720B2DA6CD8D59C8A32E5B43BCB9B42B78103BA2C25C13693AD4F2CF49A93D4B543AD7E4BE7E7304074E906D114E77C9448A8318E71F67C3C5A3B3379B40D44D9D1740C8D5003B03AA614099CB77B97A1B121DE9F75A69CCE3B7F1A2AF7B2B7702919F45544F778D5DFB9B7DB1C5AD98B36ECF231300178EF1154F4B93CE197815F6C5D2C3A3EC2A3E6F9FCC360B47D5A362BF4A41982ADE9C1377EE1162A9ADA02B966F502931045537D5CFA700C5EE0B818C8B280CDC07B1C34046A908D31CAE7681AB7A59AC371326211C170E9240B4B00222F628ED734ED39D5D0A4D1D4937202F82A531B94084FFB7033595460B41C5DC021D955C8EBDD8787140F60FFA21BFE680234BF5EE2F3F998FD9662A657BABDF4FE4A3191D7456B928BA2C0F08596015B803CE938522A88B44292223DA54BB1A0318ED439C5E0B1513AF667D1AEB8A0228AACDADDB02F689CD43FD15B8B10B12359EB83936F9191BD4671A3EF7F5998B70EBE29E551CBC4EEB214DD776AB62B20D1DB533CB2621AC0D00D88BE1E21243DCEAA1494EBD581992F6F9171B9FEBE919DA5B61A99D2D5C3A23EB447EAD4857F217CE80CC5765E014B851CDB56518160B69C56F4A1FB9542792390FF4DF407E1BE94408A2C856DAF3F95EF4E1D3CDFCD5ADC8F9BFC95F5D7A9AF8835DF3205FD81BCBB06DC10A72E57855DF16794F3B0F0EC83B36922267755777517875390D7E5520D46610B5008ECE82C08E20888C4785A1CADA07E929E1324EF710D48212B739EB5276A4C895C46BE8A405332DF93E93BED6C2075261F90DB4ABA03B4F7884B663268AE18C11479098611FCB7B0F9645E315819995DF3F3A45221A6DB71874A0585A197A01E67CBC85DB02A4B3ED2BEEE43031E7065BEB838C601BF2B161633BC9C04B506564E5DF517F1445140029665EFB724F5F5D88FF654AB18A6D65AB04E3F36720A26E4D9676148020D634FF0B74DCC828394F9AFCE7C8443097A0091FE8255F03D5A9B5AA00FE9E6056B11BB772AA6361C51938447B57F3195081483AA1DF6B5A71D050AAD3C855A770A1636019DB9EE75875D542848A285140B1B7FE054B4FFC719CAAA3B21FC5BE4B92F45D7A99254733303DDDA18AB271CCB2DACA27B600CB5CA50E857070CDB657FE559B21CFB3C596B24727501707B64A657E3B46FF1DA18842E0227C02261F88EBA6F9502856752D5D5B6208FE660645860E42A32786C3E54E8AF6DCD3E9ADB76BC0200A4A17B1CD89815301AC45EA3070EA99FEFA310B7FDBCFBE817EF28A04F44A76B38015A27985E5D9D706997C1DF67CCAC869D4FC9972AF47FE2908C3B1349728796903DA78F67562D693418345E041FC6F9E3A991299EC387C53CA9DDC0428001216637F92AA19BED77CAEBBC2BD3972AF5B07FADF92F54E82210ADFD3623939BDEB5D5BDB019D89A1EBDDD98C1B1D6BDD854EBB594516183B32C24F4C8DF07943CE77857AA0F10D27D3FF0DEDE7026F924E34153F9741CB2D7394401B20BCC53965ABDD26881FD3CF2AC83825E1DC1D1402F679BA8AC57737EDF7FE7860580424559DFD9042C88DFDE010943F3C3BFF61283143C46BA1E85CDE3284AE526CD5DEA7A401F07AF0A88586C599FF640B4B9C4C601E650D7E9E4092A940F9E4771084112F8D8A7FA8E5DC43DF8214242AB81E8FB5A097F80E01C9B436C125DD616FF680DBB16A24F90F45A42CC1A05111E062348D14C1EFFAD81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
+        byte[] pk = Arrays.concatenate(
+            Hex.decode("B42B00B12C9E2F991708C825CE3D38C6558514EFE3D7A6E03D426AC1DB215195DE8D384871BA1A63CBAC4838C7A453A419593CF2825E363EF9D63E411D2C130F650B4DE389E6DDDAF0631EBE9F6DC03316CE21485CA578417B9452AFE58D1F1A3307A90BC1CCAB8B1A82BC7A45546BB5B4E8A1DFE052750B16DA7430FD0C110AFA086BFC2AB88D7F0724CE39D57662A6F25F8858CC653DEFFFA36FC42C229D7D1133DEED60657688AB0148C07628582E6D1B16B271C95C5868994D30CF2B41EE0D78C84534B3EC060FADF2B5420CAD79030440F05169B97BE3979E5A7A39DA8A3002320BCC0D84EEB536788320DA322977B7F7710831C619B43E57CC3EA7A8A1061A31685958D515F822CBECDE29B8F4E00D93D9720782CD279495306761DBF009D9E778419438D3BD5FA31E37B12E1D223D9185EA774BF16E869FBB8A95A7447451C7AB7107E38ED709B30004EB0AD74D117DAD63F8622FAC137E7F8EACBDC1BAC30DC879B85B4B6BC0F21909A2D69BE903D288B129215C120519C34EF508A0506644A470CB5FA5E991EC0D7C5426F015EC3DEF58C6EE80C68F150816F1591DED6688415A7A2720726E63B8676717ECFD7AB3A49564C8D7DB6B18BC339DF016C4C52DDD63490CE3D3E8D211E775775B713B417B0A0CD9C312495BD03C95CE04EDE4BA99023868CA55EE642EB2E7BC915D88D72C8E1A6AD112897E9FE9B33D9E15C26F7D0903C4F7D7AAFF7518233587CA7CE69C380FE3C73DD3E15805DFAE712A572A9B374F88224EB8166559F99EF623A197FF349B7A38167729618DEC6F06C57A98F69E0474C5C0979D6FDFAB0C0E612566F281651B479DA515C6B4FC2CE4C8C7137094566D4687181EECE1CB645DBD00C248AF67C301938B292B0C69E89312895FF49C0837902416AEEF298D562D8C20AB171D6CAFE8823491095E6CD3526382BD04370F4E74D2F3A61D02F6A5F24C7203F6ADC7E8CDF4E9AE8DF7CE4C9EC2E5354D9F006BCEB3785601DFE7D3A5A58923154624C0133CF55ED97D99678AD7E408E35418363607CAC199C21FC435B5654B5845AB68927D367DDBA4989B80D69E2808960E9BEF4274E3584FE982E1A3AF1E9930933B74E99C6A7CBAE1722FD17DA5B9EF9444B57B85A35E24257CAD8F09CF89B8D2FAEB09C2C43FD374224BCC6EB3112D7E9140D7DBAA1DFA2391A12D8F2593E0281878E99BD17504D1B3184AFA54C5FD07F0894A0D8CD8D902F05EB9105DEC35FF02C8D9BD4EE9A1F007A62A5B640BD9E7F20D23C809D3B25C672853DAC600157432D26C6AB205F55C00FDD63B6D4DC2A52F931DEA1C8890D85142770A82B990E6EB548604931BE2199DB15D82326D54DF6FB7B942D3F8C21892A8CE1D0627396BEA339566B6120B36326A5E0B7E7B427AC3309A8DB96E92E9AEA728FA07C2D439032FDF51663DCF7F7548226ED7D3B76D446DCE646031431C66227018762A8D3F7420395E1E9DF9D05E3361B6EFA4ADCCE4F239CD7A4A2A9A58C78335A287082EE95111FBDAD6286B94D0E6A60E4721F9438B3D555AD5DA3819F887C8767B4709E4E4AD592C8476008C51E59E6930C941E7012B43086D361DF489C09ACA43AB38351CAC72F6A98B22AA006DAFA3830CEA0B4F1D05200FC5FFDC2E4CB467560C0B02F31A3A36A86491E11888559486985C3E38D60DCF192B00043215DFE15F6F045FAC2931EE6828815D06C4D6288D9DF0D92080B29418CD08C4AFB0CA845BD3B89D13EF20FD6FC22F1B606A6A87789A93E93B76096E516C04350B93E73E9897EE5CD40AE73B3174CF493113433600AA70090461BAFFE943CCB9FA9A07241648FAA3BC954D2B96A0953B32A9BDF086F04C0085CC697E23524F53A3D3F9B3403F448CFD69C35710E086D80E737C5C2A5C454A3054C31D2543D4AF6A74F43DE4A37C702B505CF8C63A3BDAD0FA9C4E3CC8E8E6C9F07A6720F18DDD3675DE2929B0A6A4189A19273ED965B0528C5645884F2B6C9530A34B241CE234F79B31B0AC50A022C2C841CA3D266881851D4AD7DC461200ECD02848457AD70F2FFA0E681835B57C5C7C0E041795B78B166DE29DC488FA05FBA4E63EAB9965A8A72F44069916F05D76606F6C4B7F003C2A0C5DC4955628B5724BFC184D2AB310873398F2B12F9AF1691886DF195425AE24C989122945B4EBB10CBBE58FB1BA6914780605E1903BBA6B1FA5DD679BE7ABA9F4B294AE0BBB7CE1CCE1A9E6EA71C0A88C10570E0AB58A34F58B0EFCB4E598E9AC14BCD2A8CF248D9E5876EB1531111052E6F1B97D3C744FBA11B4E16CEA78AE8C835E2150B197354B8039E6826F1E6F07C435A31AD5E61E61BA6CA63244C2005E589766C9F9B7AEBAA6B24BED9A4699003B282B24233B0821861534E0170EFEA2B78402F3A695C7AA6B04B1B50BB3857445442432805CDD8284164C0DD159292D6488C71107F30AD38155711079F99BDB3DCF36F4C924640056A881A6C8816191D3020C2AC0A298E9740E22BE1CAA7F5DB291DD122BBA689C7F2C3966DFCB83251216C9C10020BA8138791E685413C30CC3CB1DEA13ACFDAA514B3B1DE7BA8EF374F6FE2FC655E31C721E84701A4088A3D2520E1FAC5BFA5DD76C35F9590CAE7E7E1A3B2D2FF8E278237C091ADA006C48AB2587B4486DB3A7C2B3392A7020206CA7E9355BDA1F04BCB4018ACA286E707488F900C06C627A8713C6CCD1F5FA0CC10941E193337703849DC822E1C42DC1FEFADE2BB4DC43785B79DD02C506887DC9C7DCD5C9A546E941CA94622882218EC0CDFF6A24C543C380D9B21C8AE12DA257392C8114190226F588839FDF5FE959AFEBDAFE1415E19D286105355493EBEC794A213053B548763E1E90601D620E8AE36DACF2201D0C81DAC0AB168BA08380AD3733B0147385A7C4C6137F809BD5444FDBFCF0EEB3273A22A6D548D2BB75C6A122DBFD9D0D5D7C8D6C5A60D95451D760DD3CB47D98A2D1383765B55ADEDB9C78902A166692435D15A4F2AAF16C3A4A82BB931CAD47B0C1B774307F1207AF8F44D7F33AD000D239BCBBEE674A87C7240B2BE9D27A9ADA76055DE2058FC7A8BDB9A904148D9C13321A16A639E60C34026041A720E22B04B022C12B86920D80EFB1936DF6A684E4627ED0C589EAE048B0B29C9B87C0189744CE5337CA7DAC9883B28AC20634665056EB7F4A9DBF815EB1B097E4FE66B9119A7EE14547565FD9BD1747CC996AFBF716D226B4C17AA97B672219095833507017D12CA619F483D5854612ACAD372AEAAE07E41ADE602B3A5FF1ECBE943B77D9001FD4139A39D4FDFA3AA043081E227EADF3C20992D20A7B227AB38A74BAF3A302D836539B83E838479484B99E3F16E865D53A128D35A4538F3BCF9455DAA281D1A744D9BD35CB98B5F3BF4262012154BA2F53CEE688B558DA8A49918D74E9B287607515E7C03E6ACB6571D1E7A7598ED8B1BE141202E0154A282E1AB24BFFAB7F1AD3FB81E10356D86997B01CD8EFEA2009A3486777FDC0CF770D758679B682009D9D243ADE0FA0D42B126B0D19A99315BE49B4D103C0C556784CEB69286F2C6D4656A8DE6A92AF867871AA6A826F6A22BA59E2CF938ED76D19A394B4CC35DD2542E209046108778BE01994A0BD0607245B2B1DC591560E5A7A00DB3F26940D5F1B20DD417714E55D232C43C85F63D9764895E1B48724D3CF2438E28BA105C181B6FBC83F7F0C072C9E612D095496CD9723B9EF803D30590417B2BF14DAD1E623650FE5BAC74021DAB945A7A34C48F9D0BC0B75FDC47CC0E4130631E44D393B66C4DDAEEA25823E174895A9DA453BE873D13811A03E3CCE20CCCE711B8DF549988F0A6C92945167C2BA411533E59EC0E8FDB93797D019F2874A0365387529F218BDBA78F41152CA388257830B4B10E6B053494896E955B48CDCAC2CF1932C5FF2DEF6910998B10A858C3852A44E131B9C13E627D9F9A97D3E06AEC7415CD6EBF9E60C726991A084882863C5B541446A83030C8059FBE6AAF5FE7472910DF64B956E219553F36F20C50E5903C765D700762C7BA3733C8A4F404F93083DA300A0E1D9096651315922A9B90B7D1CAD41826B1E6858AA2738468C4F56105DA5A5EBE1CD9B89FC8D3CA0384703A14BBE113CAC1F6371816B9609DEC7262E5E40D318F35CC5E9060EF166314D432686FD6FA2185FC514D7AA6352DDB4C905274CBC055FAAAC8ECA658B8B728612AB1E9A3AE16397FD210C811A6C6C476909CD524DB4317FE09CC8DA9B0A14F3ED9E02EC212F5F09BEC7BF387DFC61A1FA7EAF743881BE64EA10D73D8C043AC31DAEE934EFF6F9C792D224F90CAE873130D500F82E0B0F92BEC774839380FEAC6EBCDB56FF994983D4E4CC0BA86036F69409378AC9925FE546459F17577D8950A004C7D6AB7F9635E5D968CE86F97E2B717610B08DD52486350D3870AF3F4D8759542E9AB64A3EF75684E5FBC4109E1F0723909F22F527AF953C80217187A95DFD7FCDDA971DABFACAF3B2ED13E8EBA598653A5744BFF44469C1E60BFD6E26C404B54727DF7D551C17C2752EDD5DFEDE16035EA30D4FFE1B907E62F03DDF36B58EBEF0C842072689C098CFDB78EE168178658454AAAB80EB8D4EEAF9697BB5DC3F4C6B009B093C1142434FD5EB4D0A04898475961D1C392A055EC870A9980749DF89850DA8D844C1B4CE8235AFEAFEA26C854E01A9D56C8A77A865641DFA85F620ABDB46D2D786BB5C413338C46BEF375B3C306F73D775689A669E3AFF79269C54B8F09F9A246B4B49E070482B9176380F01E8943938FF68A249F86092874F3E251EF7F52421370EC92EBFAC1C2037CA8BBEE317BC675F59D47849015E295E7371D14A52CEAA7C493778D2E33605E30F8532DA262648606C5B05B482DF14F5673213B0B93443152E9146D5A92D645BA9731C71B0B182C8351C372FFA41738A2EA872C509F0A6B0BF0113ECD9ED4B1046B6D5EE32C37A308B14EA3E1AF14EABD9CEC36C7EA2A935A28F8B4632B6C77D72FA0AB3C63EB3055E7A48BA7060D4EE941B5298039EDB8DB96425130FCB52C70425509A68A6EB65B390ABA27CF60BAC0B3ACE36F19C952B7BA7E386188FE858BFB810D05D2D65954D79B4CAA201BD0A0888E97BFB7136D30771A56A0B4120F5EA8B331A03E5F57A767E0469721EDADB30771550B98847884E7A55A1273F30516C4163195501FCF434CC925CE64772007953F2E8DFB1CAAAA577CE5DFA2BDE5488A9CE33DFD9B747BB3BB9DE54DC94C5789C9419E2DACA23B6E9408A8F889B1B820849B85BB5769CC4E617AE726B648303205F5BC40FBFF98A6C14036F7A5C7766662966A2AE34871DF2BEAE8502F01FC1F34B426C8FBDB2A3241CABD1C9E76EE1037DB7D4E2C50D040F76E7380905DDCF30593DA621266C6ED9B11E35E16AFDA74B59B3A9FF9E69C55A4618080667735EC329144A732A8816D0BD9EBF961EB4AF7981215CE7C6C7A7DC3A31DE42260B88447CD24DF0313D0480090E7B9541AB4FA0C34D9B23A037B6CEC42CA1367F6817D61BCB73A0B602764CB90523DE2F77F4D706EB6B646A715A3BAAD411487AFF8D83C7536686792EBEBBC8C1041ECA4824A7532516BAB5A67B71DE9DB6794CCBD41E61B6C1DCBAF6E942D055DF5BF4E17DF1F4FEBC17498516D08992F7E8AB2259C4B9A22EEC6ECFCE96A0A265AA281C8B9FF92CBFCDD5A8287A31F30C3A673A60CDC165753889B51607DCC362D1D636A02902EC4F2D80AC118ADDBC04A9E9F8090FF92BEFCDEADC25A65A20E7718A6A68F5EE187EAD2DF995A09478E6172304A4DA477F9249666574E7C14A32B81F3D950E0CD06CD897D7D692299EB29C089B482C2068D6A61BD2598635446130908E80ADDE107C776A82010E71F842EADA808EC6E4B37363E87AD269CE937D9550941DE113010037693A01E67B7360733449B8D35F0C61AA4B09E639895300CB15F0489F4689206513DC7EAC70155AB8762A4ACF26E64C053C5CB3CF25B8FD174923477D280998921D228EA5700CF99B5EF8B83FAF87F218B2AA1BB35B74A5C5FA5929890376C8AD0120B21F48446420A139F03FD7C371289AD10F85DAAC5B84A60F7C2D5F46A73A3371FA897B0663EF2AA679BEF8A2D0CFFD63C38BB40D5DB6223083CFCCE176EE138A94718EC313BACF241E6C0A937370C3562DBCB15DC49C241588D9A33B8A0DF27FF59953727EE87499B8F0B14C332D1C394985935E0CAD9D922915C9454B2FE235B4BB2AF5F06F6CACC6D2A9BE3D7451C292765B8C1FF5110EF8C030BEFE39575CD0CDC22A3F7D05E4C93A245F3F20232100C22F047E3B317FDA4C59DC1A5CB8C66E43D0FFEB3B8C2AE2791CA79108AB26DEF1EF73BCD42582D1D5D109D2552CA13704FBD69E5A5FF998B93DB8872FE7BE447286E147469827AB7F54686A88FCC50BB8C7D6EF1434F8C15B955C96E4F07F85C2316651AC4A1D1B99FEC3B1365F25255DD16510A58B816289010715C692D44E69B07A916C6D3BF59A6B6F5689104B1640CEE78A8914E889B007A16BCC7B55A8598407BB1DFBF05221BBE056B072D2D6A7A50E14902F7632001B4C5AE68DE2D8643C203E6281E507808DF9191E80520BEF017D858C1BA8297D4CF5F434610352F34BC9B6B27635E18EA4309F7C76460FE8DBA383B3935367DFBC08301DC88D959C1A8E8CABF76A6DA107C74F3BAC05E56474B1E6B14E55D6666D6B2C49F69030D3074B802A5652838489A786DFE5D9B7C06CD951309D5E6024FAC98C40F23452B2194AD32E958C5A38F5010914347A4A17B92D53157B113C1E0655D97DD884E0D8D440712F65B1C7A946F7FB2E915A3805714A991859C43E00DC1EB701110D41D53F150060BE7F539923F981F93DF1B16BF73CF8098703BBF19CA2249ABE21DD211B13A94B4FCA41D6BBD2F28955034A4B7A17089538953AC55B950D9054E19CB0AAACC450C1EDE2E200557A9DE47B288E34D9594218F797F86B046A3C1F24904C44D2970A1BA2FC7A41B60C7DC712480BF30B14EA1185EB4E514B9AB302C103F21B597B8D843C9554AA13B580E7D17A37080B7B520A89689E70E063EE42F3EB4E831B1501DBD1BC45BC67C71529F08B4A04B62A26668A3563A002C943213405EE1A0741ABAA4498B82E864D1FCC29A058E78E9C460477D552004D435AA389409A6840B44C8EDB9AE7B2E317E19168C88DE5DA17B2EB14624131B1E4219B1BDC7BEF34FAEF8CA54B1954B01ED396105C0D04502B104CD49CC469F4ACACC163B01769DD14DC474ED741206D72242ED0A79DD9B68795E15410AE0F01B96DDC088A62BA02E3293E182780E3DCB3F9863866DBE7999B75B8837F3D9D3D9B29ACBFBF8F366957A5EC00E412EFAD8FDFA7E4CE11994FDB208747994B7478E0B77ADCDC0C8F3357669F491BBE03E22A999DF546EFA6D42C5501A4DF2394F55C2C261279F73A090082D110282A7FD35B04AFE4CBA71D1E6702BA0C79BBC4C04AC7ED12BE85C8C9AFF2BD1F1FF7F0B406304958EE09834CC86EFD152E151F0834E6F15BF6387E0584961621A5C06F30E80A1B9F195CCD93C90D1C1D599A38853A1EEE9CEB5A2C875A8340980E12FAD46A3EFA0C2875FD582EA2000B8FD64E49381D40C50869C35C6CC37E01956558C1E2C65682745DDA92F61E5008EA91E7517CE283C99EE304CDA7FE61BEAF9CD4592E4BA70DB6178F3470C00CD17488091C751C7DBFE2A50D3256BC741DC5F77940AA2148D470644673C393FEFC570141A7FF7746E9DEF6E0084D74B1723B0CF807038BBE773AACA66CB09604B01E9D12C2460B2AFF6740CB6FF0FC421ED29E70C90805765CC186BA64A6A28FE7CD47E80D8B5351D74ACFEA21BBBF3416D697C1526CD3EB33A8F59DB3025391F410A046949196D5A25C6FD01A6242A0D2A7DA8B46CDE273962946BB92C6A19062D8FA2064BFB56EE456A72183D719D402AEE48968DBD04ED754C66DCD5BFACE22CAEEDD9E82295FA9F5BDAC55119E5D788B37CFB61C85CACF56B76D1E4998E47A8036AC7F040950AAAC505A3C475136881B02C6F8313C0F7689D6DC1CD79C80D7D9877092124933EB2697D922C59D9689C70AC3508EBC87738F71AB0E9BA3D4307E49B8E7E122D0150C9521D81C9D3A518F929E72285C8FBF4859A0BFA018107105A444AD006E2B32E5414F44BF44686B6160641E2AC6F6421D2D78932225B588A65DDC58FE04709F991D45CE2280BE07595EFAE7372AA536A1327F9F245190551351F073098672EA760B7A8F990C864A5E68335FA31A2372AF1883601D7032E56CF7295A99EA90882A6C87CC8CF785B5E1D7370BF8E4869D525E2007296D87D196B049DC2A5B3485C4B01C25EA80B3EAFDF41AEEAB1A64DA6A6D0C4E283CDED65AA41835C31D6A1C2DD3478CD92907A98DF2B69B5A3F3F0A0A3D92F637C480E09E7706B342E69B0C11D014063F8A45F42CA9CE4A72ADA9AD094267AE9EF628887068C22D6261943A988B726E66166020634C3AE729931028784890023FD6412C4924AC83A4F4A3D4853A748F59A630B52C0CD36690DB9C2FBC2D7191A023894793558E1459120D17B2434D61BFD0E8E5F680C98077E951008B2FB89C7C2A9458B9205A0F60BDD426EE672EC9C4018C49FD073F3A774EBF64596406E057F06348688E9B278B3DD628006527F0C6BD16E60D1A11CE6D598439DA6F645E9F796733945F06F1B8909ED32170780A84B932E1F3FC340D6A3750D704FADBB4EB74A066324F9C858C8BB18DC1F45A6A96FED955A09E213F8C4C2C8EA7D567F8807BF5652D2369ED2392AFEAD1340BA201CF736F6A57CBB00BD9C50015420B3AAC7F094E07CEDE473BA6E3C687D5D4CA6D363F08C7FB6926820C107C298424B35DC46A71C30B5065A63906953C504AEFADCBF40E6DEAE5F1744FAADD96E9A966AAD3D3AD954CE247CEE91635AEA9C0A32BF946D120186674A7D52833F5B9F6A843688EE3395955BA5A142BADC8F513008481A6CDF51C4DE0E67B824994EC29207C86A6D61A73BDAAEFD97221358692FDEF2A39497B2ACD252F5D726EC76EF8959509AA5E6464AC28A406A17046958FF91719649E4565DF7737A04553E4E756B69BBECD3E55A890803CED634C44F22B65EB9798370B8074157852BC1C7450355228D83FBE182700A32FE6EC6062DA1EEB9DD52CB2200FD5A837A9F2B9A097A9E15B32BEA9DC6FB2E7FC8B5B9A804911E2F2795DADD7210FF22E919BFD24B08BCB105B089E45FB8708403A7763EADA44BE4BB4E1E50B55E67762B4308D08DD4C9EF3CF511E0B9A620839980C48CB46BB720DD61D4566D48E35D2C4798D2D4A603F82843BB2E4AF441C105FAC0B1FE38ADC8F33C8D696100B771FD9020DD41BD4CAD8C676EF0D037CA58D97990E41891C7BF5CAF71916451318B6F383D311A0D2BB896313E32148DB6443E2829474FE4BD3A85B70A13465774103B620FD458E318A1BBA5620AC45594D79D2F166C0C9A1E2010F05BAB45B032B400780976C9E602937B900B134BC6ED85521E61052C7A40563E67EF80FA02E97A0BDC90276B3422500133E166747E7F281A7DA07B70110BD2918E8E6E1201F67181928C72A81042E19E4623112821DBCBD19902096EC51FD218F4AA4C312B88C90AB2A0A39B542A214CB8BAAFDA8BF504A5DC4372727900858B0FAA0F57D48AA55A4201CC326DFEEBB5413D6A6A58009E962A5FC84B2949556692C73F3D806A9981FDE3C119083BE61648705390C9AC885C2CF111E1613025BE8DD632AD5BCA73134F34BD7558681BD05856AFC2B076E6351695B134F940AB6F356196C3BD7FFA0E91A5C0CFA9A9EC9C9416D468F417689A651C376C367867EB3D6B0461AA3DF45BE0AF1220E07D32FB62FF33C84DE18330370E31FF4300F1D26445A8CE0505B3D039C633FA073D97EA80592E25A5DD15A24FB95601D356CC2586388732D2EB1AFF53D0EE23145E82363A65480F2588D90B8840F969225E5D012958E84FC2A70253FC4C4286D5FD92A865E1749063E7DB2FABB03DAACC11C8DBF8BBACD55D72E99E35E2EF9F0DABBA2306100421089F2C775D4834CA3F22BDCE29A38178B66F52DB195245B2FC589AA80BB73A53B5F5FBED849C21BEE9F27B132D65D350079DAECA99FAFA010E64029A669A6203EDE83E459249C8F29061792491DEC04FEC385A307D8641A26529C30641AB5FB03720301DF73C2D7493F95530FBB38F64844082142473AA0FF5008D787408B638BB6261298BB2DF046E909010FA56DE3078AE9F08C66A80204E939189246A9FBFEC0F884AF337EA3629ACE2B1CE2ED383D14895383262564479B6C4806B26A435791764D1DC99F65B7C27F630A4B6ADC6414DE6D3A212CF22AA2A8E3475AEA7C0646A93D0A3C509B86132B0F76596761CF1C095D77763B7D102C3CC2B00187FBEC495676BB7C912BC81DB8630091BDB23D0CBBC94EC4DF83EBA38533E817CAFBB2300BF02FC70CFCD4A5F19DB94C0EF7A77647FADDD028D1417891328EFDD6A818B4C2614B28F152EAC2550CE8FC03A21340B26DEE7535CE93125B483142B72256F1C65629F184C9F3A0550F8875C93A216A75DB26FE1014060BCD97A0AE07AF9ED03A007AE084C8E582B72172C907BCC2837681DCF3A491ED485164DCCEA3CB2C813F5D7EC2F65D35BCD96B84825755315F4C1E4B58F299B6728A6F50E393991EFC8FF1B4B82EA14E8EC1C8D57D9C58C1DEFA8C8345B5833D2175634B71FD35A3D6636270D2FA6D82BF3729339F0BB3C98D47858924E700D7D41E458260F1E6924F287AD443B0E5B98C38436FAC4115324564655FD8CC296E98E1870C3D2884241DC327E857F1FCFAD90FEF4A350F037B935D5811C48A4B7AE03E5CE87C301D381DCCCEA8817421D3173749C8BF33A9F98450BF2F108E8F231D0F82B809CDCD19E68E12787C7C780E6B0C8906E6930B69CAA26C81F29484C780284C738211788597A521927A18918D2F58CBE7E3A3DB67D629860FFD3D2CBD58D2299F995F9E3C126E15CC7159B48F55A0EDD4FBA45372A0419E7353C4CC9971EE790F10C95EA276648303ADE24F5263141470A17CD2FAD6CBF564ADBE78D676B6FE395308B5CD3F355BFD7708A5C4E1F9B3CDB809EF3597B19F4BFAA2511DA1D2968DBBCC231707A4BC1970A69EE333E1D895A204757B4E77CA712DDCA2881F154BD69D546051DBB600427F61F1F768D8BEECBD31B1485508856D459BB588BE01DC5C9C960403E8FA91B2744CA5795E2E9583BFBB1E2201052FC9C095791AA4DBA8EB19102879B85BCAE8D01D42EF4F4DED48C790B0512E346DF57E56712068085FF8C2ECF850E828DFE6C4CB2CBEADBA4027DB9B75A69EEE45594E2B1E702A0C122FEAE1B5E20704411221B3051E6EE432E1F5404C5E78EAF792C851EF19B263F73F23F8B9E5E9B0F9C51C7771A59DBB7A4769ADFCC55DE7553B3D6627392E6DF27619165A5DF658B06CA891F02CDF65D0D9CBD952C05DA13A10176217386943165A0843AB1FF34787DE2210B5EAA05720762762C31CE75F6040FE4B15B9A499FA04A0B2F5FFFFE7A24B6E12203C72FC9A976F84638ED71FF4B84C5B2C4CB0995913DA4B37A6D5138F85C3C9CDF5093116C5FEAE27D1D864E723A6354C6EC499DCFF572EAAA4055608E4714DF62626B7D5EF81DD889EF63D7B602DE987309015092A09EEB868F1AF0381B86D398BFBE8C96D7602A4AD8C135A78FA2E28C06C5BCB24B16BFA9830DCBA5DBDFAB04465759B6DB646BC18F6B1077BE530B0B1FDA9006CD7D8B96C20B174714D99837E16B994AC18E46D9B21A4360D5B0752A0C403AF9B187F4F701D7AC6AF1F90CC13937CC93FC564EBA167B27051DE27B360B299791A422E44A0B274146F94AA84FA58F850E0DA24A0946AEE4B782B97DBD22CF370C1827C15724B65835D85480228B717EB80A3266724811A50C4FC28CA8C8A4719FB01565949D05137A85F5AE159F692AA16349BCF7020583C9D1E233EDC903AD6C39F496290BB46C1F5A81A3F9DC0511E4AB1500DAA6FB21289C5C6BA780271EE09DDBD630C2AAB36050EA24CACDA650A09C795EA11197DBB35683FE3C35B333DF095171DB83874DA204CA060E2503548819B289709E7DE39A4B44C1E3F385F9C646AC178409C696E442690BC1EEDC0D155897C13F940781236E19045F8E022F52737A87D6BC57F97CE5CAB1EF22BACFDAA74FFC97E06FCAD38C0E035EF966FA629C9BE0203EA802E2653F892778856E05D2CA5E5B1A4D7A0DB4538118A75A2586ACEF69FD8ABDD1F00BB43E698F4E7B016E6A26EC87D51493D77671437D15077D72E53A19798F8B2D59D46FC83112035CCA04FC87427BAEF7506CAAB6A5FAEF0CC983652C1C2EEBBEA4BEE73B7A2209A49C791B1B5127C76E4E7C975B171EF7177DA78089FF7B1089849E71458CBD886F9DB49DC089B3BE229D2582471CC8E8595B5574BB193BF85AB77EB8182F98EE83C258E1DCD031058B8535991B1CE9098C6B5EDF4E7C5B8F2C9D1B0B451B28D98C7721EB26F82C792AF2D76DCB6E9EAF8190DAFCA29D01DC7124E61059279B92DB3BA5D9FB7498A427F6E57021AD446B1C25E1A91EC889C759815B6FA0C33562A82985314002406091724E8E17EFF2385AC10BF4DB4ECDB68779E4E49DECDCB35284B1DE1801A43A2AAF559FCA33C263B9DA565929214AE12C405B8C0AB6ED23296EEB23AD4635F8434F5347726A57E5A160802934E6FF624871A43BC1EF547FC4A8F93A86DE0D6FA8AB2EAE2ED1DD6CB7BCB81F33FD09C6FB8C568820AADA40ABAD6E3CE6DD6B47BEA806A04AE980736BD977751637535E106B0AF73D8CAE0493795088BE9AF1790043088D66298CEAE1C1EC52050D9A05FA85CE78133B8F75AAA19460BFC2941AE0617EFA98B92E23484F85765BE38623145854C7498975A35862FB363AFEEC5C893A2D2F3D459287BC9C33110A3AE0A7D8A81E60E5342697CA5D1536336DAFBB655398DA2857D4ACFD0C349845CFF8A246BD1B9839E432FD517D7464086F3A6B41D40B99FB41D2D64A93F256620258E44101426BC19616EC33EBDBCB21BAE1E412B7031BAEE075EA1E0960A26C25F505616A037B684A086AB1CAA3DC3C7ED733A074DE2E59CFE6C637A05ECB3C52C1A8E05C4C88B1B4543070E493B7657833E840487671BFB4DDC3FAA4306BDA72D9D9065301B6E389C22C8804131D8012AB1141C778A9BC5CE9904D39FD833ADEE7E4E700E06228787C285541898E846354ECEC70618EF926C5EF08980D47B9B31E03EA916595011ECF0F1C952E57796BE49E396FA1FC6EDBA52733924FFD208CF58F1662619427EC1BA38CA2B44E2F928671FCDC5FC34D69AA534C6EEF6769CC35D1A02C95B06AA081BD26EA0669C5BE0E6A76D3B706C1F4B748F767422A90D25F5B99AE56B788E6C628DAAAFBC06C21F131162C0969778E0A9B6301A7AF5C2EA923143C735FDE09DBCBF77A1D56BFCD41E31EE635071E72D7E02E84CE18E47EB1A8A31B0EC3F2F6F397BC7D5F0EA41068EFB23E6F2A63EF1379504BB927CBB638A02090B7C806CC3CFFFF28D192BD1964750ACBDCCA4960DB8C8C9958AF9CE444C10E2A824E3F373A173A627B6EE99AC2E072D8A23F8CD06C52790962F14E842D420A429B5B940B9CE21D6C690ABC426C022A05C1340FBA6754E85264C53012789BFA4F9E943232D75EFA812BD6F3BDB862BA66D161C1B7AC432708E8D70F7495ADDB14BE810455D7C20861C6FF007FEC60991EB5AAF56D069A500E94A3CC0D8506E5B2995721311A6D68D06582FC28CC35B538B5C72AFF283BEB5FBABC37481A659545B7927525D15BB7C5CDEBDB98A734CDAFEA583A8BA0C8E27946C51499260A410197FBD5556FC842AE3280F6B599D9696499524678FB97DCD2731668D6C3AE53FA1C0036BDE7B4D67036B1FE21D1CF4247556BBA73382DE2B729FC3C48D0263F24464450CD6A87316CBBED03032B25E35EDC73FE404AB849159F09585F6200AF01F0691DF8BA1B0D148B0868E33F30674192B71437BDE180CC06E2522148846B744D3EBAFF7717BABCE72374ADCE3161EDDCDB71C416BC866D5E7B891FBC2E88A60BE93DFBD8873FD7FB2B22B21F0C55F0430A1A0024C7725C80BB22B2BD85C7974C3FA71F7A062CB645E84E49CEC570D9DDFE8DD15BD7BB3295FCE161EC320A375CABB16AA7A230CA15A0168B831AB6F2D2855602A7F79A7099611154B30010070AB217BD22B6232788AC786C7CD801D7ACFC1A8DE828EFC62806F9FD597D869307576CD0D3BC4FC939EE17F2A177F88E9C54D24970316532542DDD6BE3D6AECF844E3D8B87EC50390B2926A7399A91E1735C9F272C174C49E8ACF51541263EC6FF108CAAE1393C7D0419C59F2D47AA10DB09C7086C8EA093BDDEA402865E9B2F86D4A915B58451157924CED3627D8B48E7EC24290CD9CDBD37B832EA5EEEAE213CD1A772138015E0C84C84A19E14F09E0CCBEACD1211CC1BC5D296CD861A5E3FC636F24D4F03445A0ABDA88D51F4AB66406E9C5055831121851889B80F5FD31B10DC180E1D7061298005D023210EB99F9F056ECA988837411BB1372915344D6A219604F5B078CB43119506C23EEF315DCC437A89F28CCD8CBB8E85700277C4D1BDF5A53251D97A1B851A558F852A2A133C533C5BC71BE56C9F8946EE29DB9E342784006F90F837692CD19635871663BB476D04F01ED5CD7C7352C4E6895C19F2EC9D77DBC399C1105BBFC946A95714804DD7B832FDAD2F25EBD5CE718A925EBAC711844B59ABBD3F1B777C071FED998944FF41B14F4E78555A7D6C1711F7805A7488C15F4188A719B9729318C2DCBA4F04D4B1AD66D24A0E13C4DCF099E3286B86B26F0626B1FE8D0742D6E23A7E8B34634A73A83E16A96C4CC10474940A4EB725C9663E58E6BAAAE4C525AE0CE648C64412C22639B3434454C95A2A5D04B205382F77F7FA9A2EA31736AE55E8BCCF83806BF73FE6911572923C19138AA637A2EF931E961F51B0932CCA541298755CD648C39BB3E332EEE11CFC0C2D346D26AC2C8BF4054DEC82430C7323A643B0F8B72C11D617B35789CEC524DE1CF4B89686E1F1BBECEE1A0DB8FD40534D858EC9254B3997F1009FB4B07CC7287BEAE1D507333BB1A2FB9B545E068D4D7F14D951B130A64044C0ECEE8B30B35FA5186243AAC839806207D7A6FBFF92079237050E8E1D79C0F809BC7FE23D4FAC7969931CA6440FBE5F5D76FB41500476C06AEC0F18D983B6626F8F4742AFB8C229FF455AEDFC6973EFE2AA42B6413FE007A55071B9C7BFE0F71FAC5698405660A529ABFE3DA1262B3C1D2D80E1E4BD45CD32883EBFAC7A0883CA759119403C7B1CA91D203C62D35E4C1753340218063A4F1A4643891BAB3DD0B93AE3E4A069788165E9DCFA6D664825F339AF2B84A1812E14518D72F5D7EC9AE4BEDA376DF41437A4A5AB48F29E1D42B4E237F74061AEA6606052D55A4CBAFD646C8143B67E0ECDD71B04B6472D9E6C56446E3C3E5B01CE959A381F371D526A0F659B690B11ADED54A95096B9ED55DFF91A3336480BAB995870628A2A7FFC09138E08015E86948F2BAF272DAC6279936E8799536E40EE21CE56661B54216920FF091E1944D42F9699AC16C94F4C94DBF357B9F428A06C6DFFFD2FB15AA8ED90437444581F1982517D902ACA0A9DD958D7122DF8B83B2DD15E6778BCFC16E64060F9E1F0303AA9D3507F179D74D62EF4A4A49370DAA0E6EC91EBBC968A3DC59D82F16AA00BEAF23C36E03D0564A22810844491C6A99ACD0F6F825EE390A09E3589A4050B6BAAE9DCC71AB43436FBD5EE8E939E7DB6EFA562E27AD172B3A19EEA78BABC09EBFD7869F2B991EF1ED1FFFE35E49D0B066969FD9658ADF5E902B2760C57D5E081FF40C6D8245DC229CD4435261B36AC233BC27F30D0B9E16FEC63429662A29407C8DC14F10D648DE0CB9AC8C4B2E77769DB54B96DB160DDA41BB594BC0C4C853FA97500248ECDFE42C9E5FD556B3916D74F1AB7AB8E893B65D1C36100C94D41E37F1A128E90587128D8C0ECA088276AD940EC624A7D359375D1DCC1CDA221951C7269DC4E759FB66B3C96D20A08A06C0ED75A4CF731FE92370B7F0C57DA450AA2478D4E47E9514678C74F1180996700FE0896B01B7A50F1DEC17F92CE739BFA69DAA6CAEAC9E89299C30547A37151C6DB8582629BEC83D318220E8ACA18374542023FFAE5C23AB5A2F4A6D4782DA014B1EE64459FB123C1D7B26F0A24EF31B10F3A1214B123F20DE9CA7284788EEE0A6A97AA7435AAC8C51E9B81DAE434DDFA44059510231C6000A4CE60FC320B3CA2E5F13E7423E3E3D1107937E50146F72BFBEF415F1B5A63A471DA9DE473E5583EAD0CF302098CC7F9A996C30CB98BEFBBFD365FCE067B7F421EBD32034C4CDCB1D7BA25EEAC6F00141A0E62C0784A7C06DF9D5399B2C91B829096D3948FCD888600506B447403961878589D9369349203562D660B5E65E2396C5847FEB135277B881179F8686392D6EBBE90DC5F1662545706D055D27918A3687A401B1FA9B48682003B1AA83E454E61420CE631469835F3D1EC62A31F1BFA5FDD8C7EA5A52CB9CB4E24DAEF765AFF68F45599FE21B50030A58040E8BB02B7A9131B3FC7E3E81B7922E10CB2ED02BBE796A78BE410EB616BFBB7F0341444FA0041952D4270A0BFC7BE3E8C3E1E487CC1B089841BB370E01C326CEE487F33D254181A479704A8E0CDC5F37E62BF5B2CBF6191C10B38FB053A5A139B937988C0D1B8B6003639D903A7FAA065584718EA63D6FFD10DE9F3EFC8A6ADAA39FAA1FAE9C158EE31E29B3D1B88CBADD91CA22737C8ABF1354F688A600D961B072D4225C638E0355751AA82441745DBD65732A9063D2756F0FDC4A616DF409E97E6FDE37CCEB0C61297261DB74D7066BA6CE44ED3FB671159DA5FEECA3B3406164F811A2D85187310706A42B0BA157E73B9E49121886C5A4FFCA05E919083BF691342A0456AADFE380B2D42BC5C1B5A22322F78855048674C64C8B08BC7EA509EE2E781AAC932D6266415A1786523262ECC0330A11B379EDABE7250443E510D6518BF23772F69C3C5C55351402944311065319FB819392345BB4E7C0B94330029A2A76276AC9A7EC5D65C9798FE9038597F01F4535D245DCDA72C761B5AD3E0DB946C17AFA8CB5DCFFCC8971876656AE58CC05C3E84FE97A79D0592CD4DA029B1F7AB10916EC8F23A520E1F75A56FCE299EA17E45C51D4D359700C29FDDDB143E0CB5A5B844D763A20D0991AD86D61A3AC152D944011BBCAD4E7A22830492725D0F13F75B3B55F61ACA4D9238B1B2B7F686B03CB2AA1B4DB3EDFD521D82F740AF755D6AAB2F7127326E544C66FF5A80FE1F4360A7452A87311989F8A96707D536455B60FD49542F27CC978CB414273C598B8AB30D96157422BB67BCAAABFCF4750C37C42EBE2216410FB1325E8B495712E1EE71F214BF18719608264AB8B3A8AE96C8E44C672EC21AF2E0D1D3094DFD18225BD8CD8142FCBE2DC22FA204DFCCA690186CD6A5F1E928C52D764332DE26E09C65D47CFD7CCA4709672016BE0984A6CBA0C2F716140FED65324FA6EC9E819D9B68DD2D28AEC12783465D1C53457F0C825E3420FD871714D28460685A97455C44021EC5B5118AFC0A8DD4C135CE93444DB5D7C22DB091BDC3FC29F6CF22C0DC1C31CE858F22D2204871FCC3CC7A4C48C1F70B4BC1EC38B391F6960E82A4A17C10B439F2F435ECB4FABC82EF2E0EAF743A760D82EBBE5AA0E42E06E9939E492E20C5ADDC79B5FC617B3551DFFA116E98191CC3A4D199BBF5C89F11C21B80B9F604468EDB628BCFA6D105ED704F15627C614662EC5F78F6BA090B925C8168EED0359ABD85E289A55C6850317572F8E9AE947008DE560D87E70B66A20DAAD12FAAF2B85331B798CEE0640A9C3BABBE90C64B020985737699B02E61AD6D1B1A38EF6D727586F4726F67B1FCED8871C46D9226B46CFFFD57517A86AF15A908C94F30C9DE8D283069E9C51B5702DB1C9E9607857B1188F2E7728DE052CD1040D9050F666D09BD8746C3F5A1BD78B71F0A07AF7E14D086364242C8D92E43215B40B79B189695798620421FC89D6521E7C6193C82869E0090935DCA4980C71DECE267ADFE506DD68B53FDFD88D0E5DECD06388AECEA54574461E64DDB4331BBACE58A9CC64954DEAC606AA411C85009D3E913681424EC2830E1C5F1A7AB1C2F01220EEBD52F628480D493A5665F8087179DA26C1750F9ED571AA124A6B11D8F88AD21AA77B6DAFD6F584E15EC9197887A90A94319AB0FF27CB26AC998D252AFA9862AA4BF17380C1D0987BF7B44189AFFB9FBBE317A11A0397B84496CE06AF9AEBAE16EB9F32E58B59BEF14DB05ADD156D9ED978232BC998EBEA9EEF39EEFB138423ACCF98482F58C42B5285BC74915BB8643352A7BD3AC4AFDEB0B98D9D805013A2A5706ADE4A559763A6A0A14C9289CBA905C6000B28771589751DE3D21E807CF02A49282449051084A3540B2CED870CB445B8F49D19FA91B171FA601BB56638DC9DB95E36BAB5F6573C44E26B162A6F961DFF7B8E3E0815A153F9A6487E36011BFB44F5A8F644098E6E548298610AFAB92626692EA3E6D60E084468C1A84B951F0F4E88A79386957373019C75A6DE1E7829D9E18818A202C4D52869EA8A0FC57766F09254880BCA1755697A19C276A36156B743EFC7FE6D12E7B55805A301EC45CD55EAA719C693FDDF595E807D289BD8444095E120F8E01B1DB791A8FCC43966846BFAD11563FB08AA11D17DC42888EA5AB776CDCAB9B777A70F70ED107B5E2387E54E3595448A511FCC4FCD4A4D2085A6D93AAECE1EBBFCC857C9FA18D459337BDA3C7292FB991E449D29A475CDDF3F60CBE79F01BAB5C46556DC68F36FE0A1F18076E7FB56D4BD03E4EAA53893380128A90333D52C22C6EB530DBD45A51C5508C7FCCC68EE9ADD85FD87CA7A853BA356E2DC8C56C598F3B0A174FD8F4E8C5217C31541689E4BC21EAB3CF9E623D95FACEDB07A35458AFF1197D671429FF99447FD13FA03CDCC1280FD1BC675D59068E77768D68CAFB414BAF92C18B6522563B5FCB0D043AD6D19A15E885FEBD0156FA0B5743106FC66EBA5073598C37B72987330BEC86AA6A03BC279409256BD9F4573DBBB188D7364D78A0C93C52230497C5AB0C1729954F983F39834D527FA2906429E4DF408527FBED5801AA3854A6A79C2985C2A6BB1235928B92A3BB7F6C69291FA534C3F6FB2C0754020DD01DE1648C12E34C8761EDA2A8607229EBA5AD2D163A0AE1984B3AD25C85D18A6B1499625BF829845DDD6F544674EC459C10543CD7C9AAC1449E093D6F7C0EE0A8E12E594E1807F617627015729BEA942BC7C880923495522A13ED83F945F2077F83D4DED7FB8F707E5F2F73C524B45DC545CE48460E94AEB1AF6DCD966801DC4359ED081144931D917154E61C27E9EBEAEBE26ED2A1110A7BA8ACE61690FCEDAD9A3BFC051795D79074656113DBA217EFE1A4907514B98D69751EFEF01F13FA49F6135D5D3189C691E4C4FC8DBDC8DDED3EA904931F7CC864F401DAF44C75AE13716721C5B248B54CCA8D31E54DFA1D632DC70BD5F9762227D12B634917AB1E2236E7AB7BBE2C4F514A5C167518ECAF4D03969325893D28DDC1081C2FC61115911D39A74F91E70CFF58CA59312AEC108B5328F95AB1E281C95F88697864A9491669CFBB356D6AAF715EC0BB90DC48A8A8808BBA5910D9013C0E1FB9E97ED12545773A285B0DC4B17E518D0E022783AA56AAC6803344006B68AD1EB85828FE64406427F5DE6E6A6D64804142E0FE7A968F8DE38AB4493516DBB930807A5BEC0CA52A66B2478EB3C9CA6062250225D26D95E822B65B81A97B1814BF64563515D8857B405DDD10A6E43CA6BE50CD169906725DA58D4656B2CA8D3C032923D67CA9380D0C0A4728C8D08218BF0685ACB6DB13F9E820C4CE4E5F89B50CB52F1E2271D6348DEB62EB66FD0D04CA566E884EEF8049D4369E0D06735D4BD458378F36407DE77B8AFC818AB8696FCAE5DD32D0BA06E5122A814E8E2CE995F14F8CC3A0E533A8E475EC4EA18F1089FA772AF27AF6F95EBD78F4D35249328A6F49C33BB26F8EA8601FB1C195676B26641E3663D46EAC19F44AC4764815D121195C3854421AC74016FF974931D7AAA36812BD3040F9D7D08ACC367152342F370879C5FD84EE601D8C5F154BE482773A69CA1C342E1AFF976A643F1EBA64FA285D9009D6556369019DFB0D161B358C7F92DCFDB86C87AD6B386BD3FB56C172A0FD427C677E7B94E942BE219943A104530D19FD4D78A8EE0707D763AE1610EC4485EDA26967D62A88D91B076062FD6F31750052A982AA7B383F353F9CCE461262C18A0F2E2FB0F689824A3D088F18138F112EBD2A098C04A406968C87F3B382BD8D2C0488F1A2C863D8DCBDE09547ACB86F1C3927334094EA7D21AF9D148874F2C5D8CAA0D19C8CC70B9D6F2E0ACABC176492773864E6AF4B91DB0EEBA7996D6640517B94F4C83BF7802647928005A110340D02BDD0D09A6AA9CD73E16D6EB975963A214929501F9F108B854F2F68F70A5658708C6D0E371F6B1155E845EFCE7AF9C2412CDC4278DA774F091B0A87FBBC915BEF5FBB6CDC436DE24B69D1B34A64161E3F0DEA38CB98330A735F8B4AC20BD60773647747D474F70DB8EEFABD6D1C4C17347703E84A068C4C655018C740AA0BFCBB48D786A1C1487920BB55C4310604D14723F8E61F3E9EA18FF157F8BAA930D7C2B81962A43CCA697728B0A3F77B09EAA123A4956F687A9D644593BBC014C69FF8F7EAE7BA82B74EFD265FF4FACFE58376C5B2E32BB568D9884DDFEBC8EFA5648E5EDCEAAC32F658C07FE8099DDC34987CF54EEA47EC0E99EE80DEF2149208D1F03274E9039EE46578C62A99D15685A072BB1F2A4C17C7358408619DB09AFC6C2E628A4CF8B78585B042384114DDE45CC5B005C13AE241EAA9FD522E8DE03848BF340CCBAAA9C8834559DF0EF87D7F282E94F879A3093C306F99268D9C5BFCC4E80C57C21145ADA35EF97F8DDD4B3F414778C952753B35090897430924A4FD1DF9E9D835412945A8AB552AF5CB9E74B543FA5B3738365CD4CC6386801C77659DC5F94AC14C9DB84098236E09F01BFF1BA92FE3AD8822DDA0D95F678D6718A00470260BAB03919B161352A26845B8654152EB6C9F879A3F3D948440113805CA58CE7ADCBEA4AE5CD445D61F04EC332943D683B1D124769F3FAA55407273247AC4A024F4362AF8EBC3AD99400C2F082D91CA471093F4A5D7E4136059EE3BD4BFA4E6AC4D73386A17E116369ADDCC4A8933E0CAF70C254B4964F928ACC6E1D7945FC67F61315F38A08A53DD923ED36836148DDF9A1CA8305F4F1E6C12E5E4373915E1482821E37A6BE6BC0CBD2FCE9C6162D686B7A0FAE073CF1D9717FE460BB3DC1BC05721D758E285A5FD7960539D61265B5A7B43D9AC883B1D40B8A4666BDDD112CA533CF1B8011C6DA82D396A542B4B57D075EC0D23AE306B9438C459F991F336FBF71C0100B24DC6B550C4D16018E12EF4142E2126A71BB08066A0F77D5ABC11C4BFA3338B287627E487834D77890DDDAE9988092CAE85A97AC758CBF6F56A50F66D102B3166D108F333536651D3F62F76507ED35CC9CDDA165A6575651F4E7D469EADAB17E94ADF1F7796F5210778B0A4A47E290B81861439BDB6D8EA1BA5EAD0EB04885985198443147BD7702987E75E8D722A0494DD918FC5F1E514C2E2E7B70A8A3C72D3256A8EEF282679A3B34937A70CDF4B928B35A0012FF6719F76205A908545A4D87BABC22DCC921E4B01D8D001E44A0277F12FBF6131550824DC232D2EBC4046326A95D372C718931569CEA22E0C0A0C05CBB3AEB39B72B9D78B57F893EEA79E4E609E1B6760042BA1FE4D79CD49A664219203C35E4289354FDDA0F932D3B83CC9DE27833535A6C989E67553BC1CAC27F3D61F1BA10BC5E2E99377D7341B07A1D706B28ABC9045A56052586C7A24F398D0A990BAADBD26FA27BD52A8D3CD0A262A1E2507454CDDC23DCB107D6FE4A661103F3D567C383B92B15043F2C9B53B3050ECE9A9E457CD392DEE044F3202F8EA960E922DC5EAB6322F9FE0A066FB5D4654055742817F49E60F6F8D2A7270FD50FC59D0504BFB9F7D537FF3298947BAEC016E9FB3D713A42797A0100043FB14C946566A4D5426E301126142A830B67F72F6B38242F7E45C096A2ADAB3224EDEE8B90FE8970103D4EB25AC9D09EAC8D99793D6318B62B77D7038352CEB705197462B804C5BFC87358D146A7653B62FA40257265146157E8191CF0B8ADF8E6059E4EC92CB21D363A91A5DC34660A3824334C7A5EE9FD2B89007C557A61D729F87E9A51FA0674D4345A1E9491C39A39FE0899170A8226108AB2F3F65C5032233DB65A4457C058B5A82535E69A6114CD53B8B6D3478B426E4C90A44A610C73BBECD1812DE07B9CBA2206D0C0A65CAA7ED404D1E2BCE6C6B85E9D50859D9523126490ABC159064C7DCE28119129A63899D408ED8182ED368C5466453805DE489E76DE8BE29095B06E0E585FCC527FD7A0CA4FF3AAA6325460C78947CB1BDBA1907513B047ACD0ED7A8045E94827DFE154FB48DE3FC0C6B56CA4D2E25647DBDEA3CCD7E44DA683574CF8E2321265B4A01BDBA47DC7123BB7E0EA614DCD76E3A6B9ABF88034B1510178D55ECA1CF2E0E60C9DAD063AFFD53C06D119E75332520624236980FE9FB6DF227EAF66B6EF954DDF4B2EECBD370389EFDA4612CCAFA56738917FD6164F132A4096FF30B67A786D90059B9E51C88216CA0F7D82CEBEBB9E00D4BD6452E4130B30B036BE43CEC23C5DB8594134D2D3C11C1BAE715AB8B682595ADB80408182ACEBC6B5103B8C2FFB2ED662948888DC5202B8B6C44873CFE3405BEF8253B45C3D01FF1C287EF48554D7F3FB9F3F18E969020152B6B8CCB7067A4567024186195B8741F8A0630E6E2BEAF9D362649E3482437BB20E044C0782A91649648CFB6395600A1B262D1EA3C1F2B28AD081B93344DDE6063FA773E21A14A22C943635DF64261FDB75DBC279B7263AD75108B34B6558549F5ACC458414DFD0B045EDFB0846351A23E8B7AB462978890F3517615A25998426D88F5557FD3435548842945A11ABD3F515E98BA75D9BACB69D07071BFA06FED1F61D5776C6F7650513E4ADEC987A5254642722A1022150B1A20B703B43A2DDC1D6457493CB52AF9ABAE383C4FB491FDC367AA1E3D5802480FB39F6EF92F3FC8715578883EFF1E4D3777D02857EC7C3D7ED28CC9DFB7C02283339FC7AC12149AFC9006C0E6F2588DBAFD62D1CCB159640CC1B261D8935B839CE1BA325ED2527C2ACCB6BCBB82F06691ACE1B1059C1D95BA835EC5CE95C75638AC53EB886815457850A6E41890122D8649C427EC5A17A0E7182A0D2E933CACA11B9F7195E9135A614F37715DEBA76AF9858EFB5BB6811F2D6B76AED0E72A644F403E5B6DF347F8321B785B4C69D9B8E20B6216FE3D8CB27F00EE151007F5B24E65C988A8A623948826E5F9746E3030C786478292B9AC6B2263AC8AB1D718DB38B34E719648EC9AF1685A5D0F1F23FFCA34D5BD35A207E5BBC6F71CF1A0E1404A5E7AFF88A670C0B7BC49D896EF26064B3166C9BD34F1E3C83526DF18D2A8A51B9007F090C4CA6FF2B48C294F2FB5FC7492A452024BC1FE44421EFC2A4E9B818129E3380334E5A442328FC2FE796F6615E43DBDCB7C9AAF7E0A09618D822B3E6389D17F225AB39CCA6E9E9B161089BB412AE61EA07186705323D98C0D5320E529C0E914AFF5635DF50A01695ED302D7AF04B9A8C10D8709B19C637A50C5AB63F9F15C10129DBEDBC40E902A4D0B45FECF3FB9ECC299FD5CC5EDA1350780CCAF9F795C0711BEC987172038FDA73320DB60E2BD45BB4F2D5F5C0FF8DBCD3294E4B8145472AB6DBCCBF1B3DC2D59316926482E1CE3EC3CCA654680242EEDAFAB96750080118A5B14CD7F790F0FC4B9BA68A0BADB72949A8B52DA98CB898641B72E3C5D5164DF10E2ADBB469BCA11410A100F0AD52CD5BBF985D9E4E181B6081819CE66C6D8E802ABB808C1BB2246EFD4BA9B0476FFD87E1E9C020B2B346785E8D52EDBDAADA24D1BE7798D1E998D0967946B468CA65DF4B7720E1B332191FBEEBC0E2DFC2F37519E9AA14C907CEB1485AC74416E89380CD03AE0C30D7149A4A092709258247CE6482F2992062D5508F4183F970A78D0DD1E5299DB57E1735DDA1F64C1C826FEDA9760E49CF36D9AB60CB8975EA221EC39AEA0508C024C68EEFB3960624E15908EF0D84AC0B49AF5316A4495697C62110C60CEF28171B96370166B6F751FD641AC8D579EAE14006803A59A11C2255988719E5858016846AEEA04BCDC68249869F4031ABF572B37B97655C94E065986D3B98E54F8AEB998EE366540B4834AA9CAB1ADCC286B12243A53BC9250FA058EB346B7BBA63E78C08AEA659EE4EE121CF1298D22F52425E67FBCA09E0A5551325C272B0C418D477C4A1DAF7496479895514DCC670CC5E4150E069E1CD962DF358F85AFF01CC0D492A926C4547E0926B2FBCE94A1B35153468C3DB0A5703F9DD39AF35C43DC4F579268E20DDDD30ACD88A64C9B1B6A1A4AAA4D91874C12B1C39DDDCEE1B0EDBB5AAF05C83992AD610E71756393C55B98003F15C22FB4A8F269DEF1F7DBF22957048F616E4B3970D7D7EA2C5D573D6DB8CED639C6489C908FD962D45D4892E3A4C66BE95AD966E37544D9F8D04FFBD03B7216F3249363BA8AE7908A1A8EC17B9BE3D68862F786F7F67D06AB395E251FEE2F4F2CBB10273B5E86390B384019A55C6512EBB6C3D03F05B195BDF5966B07B0989224E871D958CA6F233267399283568005BC343DC4C77E99B99364407987FFCAEB4BD0822C522948C4E66E9D423AF8C814F2B9D8872204A6BA18E94828C4904AA88988C04D8F4D63C327927242894DAEC4876D64704F52038450E8D6DBA49A2C64197BFEA766B8422F1C97C03F1B00EE28BF74705AD961A368D59B4671105E3FB7723E6AFD53A566A84E0A34ADC2D1010D2A264304AC6CA8B791592AD0B9F4ED14AA430999D5D4E0A86CE00EBEB27DAA63D184C14F5C85F81E5729C79A695D317D4BA730B4339E48045A2CE4E5752BF1B04A0C79911C8E3124A4611D5F6DFFBACBCE1BE0FC51792DE5FF3496F859ECEAC0B17D99FA785B443DFF0985D079ED4767B1520509DAA9EDE96A62D1629A1D1BCAF5330615450ED65F494F6B2B5F78F62F3CF9E2837E317577F4CC400151E8E2A06D99707105B1A92AF523C5AB9387C04D1A884B9B89157CEB97F97BCA58777A5A3B2D7E01CA3D95AFE0111C05538608A0026A8917F3A7A32B7C3F73B86219B439D6B488F3099ADF5B50C7AD5880EBA24D3FF2A61C8C56A9605A4DFE4F7871477EC762FE7F7CD855E3C6EDD7B2FC1D07E3EEED0840D5B8CF4D1D56E2DBF65DA7594C290E4813D49262A570B0E4121C26EAC786197A6F9F5D35E0829523AD1CB86AE09A472FAD8EBF04FD088AE3702BDF9D20CC872FCAF8F3B28439D00A990DE7284756EBE57AE66999222C91DCC455A8DB69596199E00FEA90EE6B91C58A8B1C7245319CD06F265AD6B4E1C9ED828764CAA4A1B550FD7A46099FC591A1DA033B84AD0480A025273F97A207368B25A956FA995C0F325D29FE4E7AC34937F39EF1096191965869ABE7FCB216862F5E3F49AB1760866C3D8C6113833A3A11316CBFEF6EED53E427AA22C10D22A423328FA08B7E5808914B02DB38F0070ACA0A83ADFE65E6E380143CD1105A5B542E1198D7FDD2C960875AED0E51CD6B7E6A10ABBCD98D6322BC2D8CBCE7AB136916902F82C931E3B979D0051207502677AE625E1E04F05296C99526D0025DAC68C2E29C0D34DC44E808A533CB0DA28E3B175738E7372E63C4FDAC6B6DAB7FC5FA5E2B05E45D95DE25EF6920CEE37D584AA48960362DEA911BD3870B555323DA90928B0D7E08E34ED3D8764B03CB8DEC85382FB158699C1AF10E61BF4F11B4B5E33C3CC803A4F0FDA3D9F6C424DF5015CA50FD028427F72DF0B03E6187136BF3A6A81411B277251CC051C8591A2ACF50F09EB1A55D79ED0DB7534B262CC91B4C962952988BA68AAB01BD8AF1FD9EDA859C218324D63D3FCB2CE836738045A472659265BEB13852E03CBA7FD0DF06973372A13054E807F581A4EB62DAAD9662C1BD8C26921BB5A7B2CF07647716C79975C40BFA90DCA6BB0D5C5705BF30A8C7844FE154D1A434BF0EACA21A62A25279AA6F0F4D8C770076A54590822A3A43BF47565C71880C041E713370A55AC2EFA85374A9324469FB1468685D2A30244227AF9691F904CAEEC6A40D370AE4B0E4D4F607FE63F0C92AFA56CA983930420D2501218FE9B75888D89A2AD9A26CD0C7ECC9D7E88DDAEAE8B9322E7B99CCCBC393A9FAB31888648B5DCBCA0D5084A1C7E08E454E5B897382DB5CDDA7C6895FD084B9C2698D1F2FAE3319004BD7DBB5B90AD53039C3A5EEB59DC321DA381BC55E3C4B24C48217744FC225F8FB36666CD4CA270DD1AADD6B7CC711166668CD58E002CEEA65BBC4CDA25D387C753F40B611BA05C0B98CFF001968CC1DB2C9FC29046312F7153EC6B3A0BA617005ADBD2F508510957D07024A0C2125B018F33B20AE1A46EBEAA9EF6F4A6704774EAC069AD4099B05210D0F8CDDE26358B5147F9E13C21C86DA4A15CFDF15DB8DEA6FDFED8134E0F73236B48653A57A50BA396380324578E4118ABF81DB1195A926C2F52065CB158D52CDD92479FDB69547E17CCAD2F01171641878946EDECB54EDA2F0F0CF12FF4A1879A5B89B9AEDE66158B7AECDB0CA93818F6F43E3DFBD60CCEAA1E9A62F0B805ECFAA4ED562C94D44294312F4CAE4A88E4EA5C075A886CBE9817357D5CF4E2C93D6DBFBF6AAF13ADCBEF84B512D03528C398896D5D32B2C733E9639747B5424EF235BF10E0E6654A98BB2291121945B8F99F2B58F827A36BD3A0785A47CA145933F504E21474FDF9460BEF7C499D25AA65F9B82593EAFC64E428977C010212E14056C674AE7D720B3BB6B013F6C87183E9C6087F7117BD16BF80CD991DCA0B24A13D4A0F414E5D913D7346951F06ADE5AFFBF614D42AA2F04B0D6C65EE712B83D9D6691C065F913CFAA695F5A8E0D1213A7A257052A976C8A99D"),
+            Hex.decode("B7D693FEC1B9F736E398D0AAF601853906B19E0DBBDA1791BA214F916720D0B1CF4452C646F40F35F49C7D628D7FC3AC118E02F4495D8740696A778CC2134DEAAAB04BEA037F302B9E1B551488EEF072663C0A4303595EF61C3570D8E62822D03316F62F05D4041B9D2F709FB55194C488B471C5ABEF583CDCB612C0C20A780423C56E5B50941FDA49E358EC1C94D97216E21BBD2396243AA74E8C43BB785D24436ABC703B1EE25D6217E20B75EB23E2288B4C06CAA228C88DD70121AFDA5E992505B7159EEF88F4DACAD134D9E0882242914C0BAF967060053FFE158F0C750F496094DD93E40EF0AC80BA2CC3ECB772DF222B3513345C29F58D7C33B2B6F4B655E0871F83836A651BAB551247647B3B264878B4D50A5CAABE401F29F1FECE675203BAC2F8EA322CC25019426C2F232AD43CD787DFD4BA01865CEE02B2556A237344290C95C4B213B8C9A3B9AA2F038B3B7066A9E9C39E345CBCAC88F36A3380BF1694C5A4914E524716CBAE16335718F5321612C78A1F60A51AFE18A68B6BE54642333B850B7E7086DA2ADF187A58B7BD46168524656A0EEDB02B4430B305E7B761552FC5D526B76EDEF7616C06F48FB21EB1E7236831F0BABB0CFBE52D742D4D7E02AA91BA6FEA25BA48A6EA5BF121455B46CE7A2C1C470C468C9C28C28C5AF862193A0CE8380D5D3F362EEF50C459654C6C12D29AD9B98DF1D81C1B128E072C904530FA4004D79668C8B3E9272EA8BDE16FFC3883F10E1C7DC12E9F4D997E303CEBB3E4BCC21146265FA2EEC969FB4D34A32FDCDD9B11C4FE1C8F543F288DF2214483CA2883BAC0A7F187B4B165EB36035844E2435EB263A0869D1301310C2BDBCC60444314E31361D1A92B697CFA7590DDF38342A4D91F33C60274C2A1C20561EAA8D10202A7F98D5219951496816C5BB21E16F8D1919F5F01FEE42DCF837F5863DFFFD9E47A8FF7D34BE9CEDEA16CFB0330AA8322BB019EFB11D2DA94B89C59E9E52EFF6154438F29C803C021CD4631938B01EF62453C363499246452E1A28F28259F37A9A109F2F6361AA2723FA85A86DF53E02A467EB1C6586E18DBA804BADB513CCAA6F0AF255CDCD96FDBF0F181EC3DB5891C353BAB34B67FD7680E2B25CEC91152002FB15C81360EA230A0D8FB548984B71B36E49E4212CC0EB5BC4540CB94A4654DD10CF213602EA3F3196DAB14016F5F914A69E444F9ADC02ED40BBCF8299A6A3FD94DD170152AF6A1318ED39DBB48A62BABF2882854A23C7F682D4E5CA9F98FED9AA0AD69B9E7A332735581EFD3E14ADFD061809E74212243D5467C7EAA4F8D81C4300285B2505D4FB6C57498A5A4695FB27B22EBB299AB64ADEC78C06955D3E57C9BB5DC424F6978B257B16D11EEFAE0DEC5ED81E4321C3C55AE7A1043D3290AE86C1696B87ED70920B34E0545BB501084C7BA096D4E7E720C03ABC489B9E1F041242491E209D77EF3E361C2A88B1DB2D342BCC7139A8CC4D004A4413B8534768EB67D7529A37731E1AB942AD5385AB887DFFD43EB6054F3595A71DDA5DEB62B083FCA95B5A440FAF4A288BB8A3042F21AC053798FCAE2A521890D274FBA7B4C55316156AB281B2F2D122CA6492588E23DD02325060D3052C9C6BDD48D54DCDA92E376EACA8C854E1156BF98BBDFB4A80D845BD8BF4E2ECD1FD33B90DA8F3CF31BED1F6D91E0DF368FEA0B4D5DC984A1D391E43569BBB3C1DC7C8AC090C43BF83F8E15094C7A77E191450DF10A471F4E980268F9B973C3C09B04E9E1367090A1A88C11862B88DC9BB20F4DF4560321DD169320867462D61E3443F4C82DC9C85332303F402FDC8FA1B1BA99EC0BB2A86F15CC7D6F4A4E6B739262BE04DB137907365E6DAAFB629F1B496F23D75EC66366044AD72CED438AFF5E55288AF7233D49FD503D32E9F09F4041B0F5D8BB5627C46460F828D6F40B0028886785E58C928C9C3E2DBC35A9515DF4F7CA8F7DA8AA8606AA99FEA5306AD791821C991880F208DBDAD7B481887AFF9C587BEAA30873840B754CEBF50BB5C9F670BD4749D73878A779732918B154A4189B94A27A00555398630D323B99BBC627CC2A4ED8BACE7CCD29979E5720F7D384EF76B1288F23B9222A3E18AC9B4A24D8475A4CD502BF2C1A1C4830074532153E58DC2B9E1A0CA420016E1C2685899584950366F4D1D1B6937C339072304F1582C28DF3F9F998416752C5CCF94B5E3769AD3D510E5C093F63549788A2A1BCDA40E69653B22DE4BECA4D2CB999E2B57CDFB1FE4E1C8B3A81621C4FE325E6B01499271747311AA71834094AC86F5A145756CB6E1BAC9BD450CDB8C95351D0F8D2FD930EF8DE85FA16C975FB08BD56F1B1C7898A28A58AB33147F6ABE39D3E2C49FD56B42AAE37C6F8FF02FA5D4B1E2F56F5541AA0E82EF4793E52A8339A1E851C03ED3B3FAA9B6195FFF8597C74B8B8BC61AFE88CE6FC34B859455D2A603B4DDCD02A6428B9B47279B2DA03784D196D5C145F70B2A7D14E95C25F06FE9AC4EE53E3F85BA3DD8D507B82167D0EC1A03D76B8B5CF285CDD6F650336C5D23851D13157696A7E6FFB39234586C68D71F7DB670F49E4F85433A746C5F0018985F0D92C131CA0130F3CC4BB4FA70230791ABFEDA9FD3234F997115CCA7D7B25F8E7701157C61216BF4906692C75B5CE791813BC9388A80581605B4E91F8900C02D9B202B3816B83CEC1511360E02B8CBDEDBE38FF4DE9EACC79D721A4C2041C3E08B8C9AC2A935EE1848483F314223EEB54771D8CD0540064AC0948A9B07C6174A79181A9C96289B8FE83FA0932534AA012AA0EA579FCD5A15B0E0C0ABE88AA48F58B7CE841FC3B21218363806DB669894205182B7A7B27BAD4B27B4F697D916CE08C49ABA3DF2EEFB238CB00A9DF579D0E6428BD670B43869C6B863276C21C2592DA5F8FFA09C22F160624600443E0C43172CE55819C9608B21DBACF3CB084C7425C6B42928A9DF337531144CEA430246B88160EF2214E6D3BF0C0BD136A2E92602E4E7B66FAD1F672EE5CA7006ED0AA386A91881B9CC5F17431D477EE9742853DAB77996555CE875819703613DBECDAC04A2C711C00BDAB0EA6EAE66C027B2D19703C37A83517F122B11195683D27726C072FCE077CBA71BDFC88FAC89413DF758447C528364B35B3007B15967498577D8018B2C1494C93E51CA2310F9EB2AECE971FA547CCDE634A455D9469AFE5199BFA81D0B3E39DF8D9C8032B12952887E5EA57923F965CE52C3852F6BF5352E28BF1381164B19DD0199FFBA144FBFF470010EB8B963F14704A8BD6115B3BBC8210011AF0035B2D670EF0E3C20F6915A814B5C27987836E603E2A5F5B9AFC22EA5AD26928A2FAA44BF0DF7E64A0EB4B0F89C862C247B836DAE9E6348436F5D81823E79489DE4D90F12B6F63D7791EC8EC43AB83C703516CDD2C39490D2484595C1EC35449E5D5FEC15088D8FB511F71068F5EB76B940C1BA3E54B45317F2282A90AF18BCE6C1F756332122816D17922C2F5517F1EB15B7BFC5B111BF9038AEEFEC531734313A196BB48D8978F93B502436CCC622AB3E6386AA8514A2BD4AF01F6F8BC5727C4DBD621926FDEC00F4324420F140CAF915D64C05281039F9261C35FC004C1D5F1237907A9E54A58488D708619FB55807DBDC430363F29DB7A1C1B389F678AD70A25E3B772C700D7BD47370826D898FF6C9A2EA69D4C620B08A2B704F059AF69A12634DB0C461BD84E9B2E7B0D689440A01F0F7F022524BDCD1D671AFEC2AF840C169E6AB4F7C0B8E188C773354B413AC7073916EAD262834E3B85AE7C7BF042109F15C08B74B19023CDAC1DD3CCA7B976F63384ECF63E406307E1812665AC68F44A920C7C40B7D25F72FBBFF2E5510AFA8F23FAB67B42B8F198492BD1280ADC1F7DC6B801149B0B7EFA138F75A8D8911B2EDADC36C0B0C361A2737E99735654ADE07AA10C5E3E0068B3309761CD91A9217A458D20C6D5191747E31AB3CB304C00F429F571E2597D5AB79B2FDAA44E2C251FE965B6CE012E06F52FA238F2A9086656C66615EEA52D63936A42BA6492A033D300114C87650BBF743CDE730B90FAC5775058E9F35267B8815644DF23E4F8BA8CBEA1547BB9C49B8CE5F7BA5622622335B72E85C15BF6D0B78B893164466B42CA41693A39F5D0CB08221153D1AC0530FAB7319EA984367F889722668D63B1BD043B4112526E2C866B696EE866B2DB73836D500AC08237F0F82DD9785DE2C63133541EEF75212DC1A362C612719C9BF56CC6DA5EBB37D9F556B08452C8120E31F1521C71AD9C114AF174ADB2DDFC9C04F6DE2311FE8170AC2C0503BEEA8FE9EE55684D9E80D65381CE42EEB795BCFDA84BFC399B99CB4C89C5F84BD3E08095B7C384052C5B836433D55A491982051D0E04E205393CBDD44780B121505B4F35D7A92465EF1FA69F92775C2C037CA476B0497557B72A3B87A13D0020A9ECD2165E8839BD30B91876E83C997ABE7840D56EF147C83319959889A19032FAE624B073909CDE272206F7C255F92B1702AD31FF4AAEC9AA89218A5C063853AE0A182C18DE549F8ADDBF64AB2688F2A01F9B8140FC4814002033C6313420167575157BCE0A27E49672F4E460EFAFF2F3F46C13291FD5215FDA79F1ED42D44DBC731A30B9B16FC9465A3F321EFB23123AEB13FB008F3304CF74D5B398AD9C46623F52D83D48F71C0182B7A6EE12975BFC8D08D936890F5EAA00C7943254527FCE8D534C9D1ABED1E52DED11AE07645E02CF5BEC3ACA86DE735831D927B57148D4A263E1C4E506284696CA62BAA580F18786543E5560DF4AB92035E70D127E57C477FC6A6D0458F613BC5C958D30C2FE6944AE493A03D11171ED5AC20A6B49F5BE9446660B872BCB1C53902C5984359BA5B21613F517DDA32A58733AF8AA8C1FAC92BE3BFE6DA25DC01FAC4A7DE6222FAED65A8076FCD4B033C6C61AA4A087EFD9A51CCA18E69F3E2D51B3630D3B59E043EFE7D178FA2B10C044C9D25E708900B61212D8ED4A018274141C21F20B31433AB5AA643F58425760AA520FA83581DB3356B8A0BE5192CD5BD18F7175FF411A1AB13559E5DCA3C686F92F09717E89F493C52F024CA4266B0EDDBBB73C6CC62BE61B8B3AE06A73D5156CDB0810A2243DC3197EE2967D7556E8014D07C9EBFAC292C5906E57CEED8BEBACF7468857180B4BD6CD0BCA185B77FF8F99ECB02A5544B580A85D5ED933AA44F6DC198E251FF88F93FF6F00F82377BC7CF332424E5431EF02B197C6965626CA548A722B7701BC7ED02078932967AD566090D787D8A03C7C702BB3BBE1C4B4C20FFB511BE425B4625320D11E46139E8399228D064934E00C6AF2C58DB9C2D27E83EAF01D1209DF98A32A08A24DE0DE955960765C5A5382BD89820D2DB6C637985591510911E418979F34AAF8F250A579726505961CCE6DC70EA923BB7AA25A8C7ECB6F3A5D817B713DABDC7561E8CB72ACEBD25554DF650CC9842C9A4E89077BF2BFE0A5FB224B3A730C23BAF731E4B6071F5FD5C104EAF109F0B070F42393BCEA09C9CE691778ABA3DF708C7C75B70AFF15C3058D259E5154D6D475EA705124B10F8A8EBCF0F08B5CED4363390DACF9F2CA81F5422004C409DBCC24228ECD068A05EB94C08309B9837EA83229E0575ACFDFFB05C79BB58B20EA071BAC686E84CFF7895436E9918FE6A3751641C80E6C169A4F3BD6C803DADF99A1BCB3F3564B717525134A8D948CCC44B7BFB278B29861066A4ACBCEE3911D530B75F06F9234BB3BAC6A74F82AEACC8CF258B52F17BF01C1D2A4915172BB53CD5C27A17F576D5663A22660270D07FC09665B6EEEAB0A83F0F3A2C7162301759620971B7F38130517463ED3A0EE3F2DD7994AFA1AC1DEEAF61B9A88477BADEF4F4A72FB625C356A34EF2671E7A852EBD3F0B44DC331046CC17A5F260C4D4DD8B487D6F3BCF424279D3E2BB2D8BD728E41E684313D34F34C6B39A521BB8C620CE31F35140F7167233AA3115BD80915C7D4F19479FD744C7CC790514DA7A08F17FBA051C7053D5148310E469373CED8D72AB806B31A116A5F082B589553571699B6D20E82235687D0BCC6C99BB479D052E76C35B5514B2878F6F3327628E1FB427B2B197DB1BBFE9B2BE7A76151EBFC43A35BF9949CA93AF3B296638DA55D0C55AC088F0B3610A3184E4D8452C79C4D15868BAEE42A9805770FAF7694697F2D648A5FABECE9B259E7D294617256E076C734BC620A2A29F44CD69EA67C890067DA52BEEB32C427DF8B930B6FE5790CD3CBBE58BAE808FB1569A7F769A54A6D86D5A976C5C3AA4086B493724220A852B60980B431F86E7E577F9006C0CF6C4464523BBB216208835B506FDEAF43A66037539690E30F4891DF118BCD39259B8111B1F587AD34319CD5CED5CD0841452C2F77A49E460C13765512DC84A68E52EE58B64E682CE59CEEF84521E00A6586C24E7E1BA7489D585E2AF8381A5FA7EDBD628555F2A50B22ACA60BC3EAB1B103D18A2672946B34009A2CF4C18609E25322F253C68C5593CE26EC7FE769A34787E94ACA0CF948F9EB5F7B058ACD0C98CB132905F916377B4C2854BA478B92F6E635722F5C6A6692B3B4D8F8D47CDF7023DAE68F32B4DD49F07C5F469D8C0EB1802AA1C2964B143798D4B9AD345E6A6A0C7459D54FDC0292EB76A8A8C6880E2BD4244310069604F10421A8B5FE6109C6BC17324A0DBDA4BD47D28E124AA3F28CEB026DFB7AAD216C2F45828E29FC40137245DFE8D84833ED979DD483DE8DF9F6E1CB1D7BF0BDD6BC4ED61002670235F13EDA535964232F33E720C0654C7F9EAE19909FC01C3F3DC73F6FE36BCA20F3113A128E9DAC856921C0B5A9245A259DE51198EEB14F2F25AED1D867DB2A7B8A762BBE4820B04DB746D234BD380C65D4821B36CB805FEA63F343776980ADF72576CF581603E364FC271A5ECD82BA703FB7AC1F68627DB5055F5CF098F918E096FC143A71FE042DC1C829F3D109A753C7437C35D2728C70CE2841D601C6A635095D97D77B27DEB055030F5C97076D6BF7E543CAD3BC937CCCD43806C503A11BD4A69B6D13A7341F86A003FAF1DE5626F961C47E106FEEBEC16D8ADF51B37B757164D844C0FA0BB190B092856DDC3E798B35B05E08A3A02AAC1C8E861E71B7E0082B06D272A4EFEDBD5468E289A8BFD9D78025E2C023DD482A94C72E225D684FA8772922BF23EA8F25780DCB9DE8FF5D582A073691BFDA6B928D9B31740F4F4CC28505D3F62547720489A33FB7BE06B0BA60B96F43FADFEAB49B6D2FA46E7374577F1923992DC5E56E4E7678D3419666FE56F0FAD4CA963FA1CF60ECF377503353D165A34ED7CD9C2B48CD303FB53D1731DA5C1DB53F29C72CFAE501C07D125A752263240A78EEF533FD30301C8A60D8610E50AA50B6B922853D5215D8D85F447F7177A5A01525635FF1AF540F82211160B7406A8CC2CD7D8A0E62DEACC4726360EF91F869E3AE533CD67147BE49BBA8582B443C5AADC8803C5754B3C41AC6107D326F9C0B29650DA58F718123D6C375D261DA58BA1C25599437DE440FDC67A2CED1664E7685503599D593BA314D265884AF6E135013553EB6CB375D121D185B29D93ADCC2117D405473978D6A4BB24650CEDA8A9EE42024E7289254F86AA48E6A0163A0E1568A574BCAECEAE688B04A4D9DE997169737DA455A7F3C2057FC7905EB8A82E1AE3D237BADF5BD8A42E158988B3D675C9A4C96BA6A9CC13BE11E9B51CA62A377298D45C557FE72A1D822809996EEAC09676433028DAAA7C4AD21C9CB5A4F825AD98252BDB279C6F7F1F985718C458E759C54CFCCD41407279F9818CB72C100B27A070C72B0B1CE12D98B4412F4185B2DA508C103C5A3CB620B593A1618BB0DD7A598AD770696C60B02C62CB3428FAA34591C54FBFDCBE733658EAC82BED36D9655BBFBAC98AE7CA1BD4B45031FBCF670D373B9DCA8F30C5E8670C7B6B43ADA7CD5B3F228A1D8000FC27A3B74FAA7E80AA1B78E625A69B12D8DBCB57C8E03F61570DDC0526E5A8455E1726E542DC50B3C0E14464A3C94BBB683F072C07D8B8445FEDB59FE7CB606336C0CB4B0AD119A80189543F66F65E610D09FA589679BD689E4D23D84516212138937828C7D815AE2E4DB77DB988AAA797C239A5372F268ECE25C214E8028C12182B90329A6058E387218B1562060ED3887679C04E030FA1FEE399C72A2AFC8B5796B7AB04C3EBA0E25C11B190D31C5A066ECF409D39918049E9B136965AA5FAE3CE94785AD412F5A0F26CB08D3E58A4B1800E15A794D52AC451B41A93DC1FF3C73C8391911C832F9C3AC0A0161F94C173F3B8EBA47D0D5BF8763BB24FBA6798169F2A5BD66526163C1C5AB847E86FD532692C54708A8723B08BF2472FD1B4391D3096A7315FD095F7D167BC7FAC42C6274C1C49E4C4A81DBA48C77A27C46E17481F2230B42226DDE8996E41A636BEB871CCD3A565935BDBD1F775EECE8A50CD72DCBA2E38FECBD18B6C9996D5300248FDA0290EC27A666F9306C7EC5EB113300E35E256B9460ED3CC619861067A925F00BD35F41DE683BA58ACA53B910CFA7AA0771F1E236323DCFCE4A81CF7073BB4D2EADBD748EC5683AAD977101947F5C98943B4E146188EB02075110DB87A0311D22F51D94DB38CA740F3AF55AFA7E39A704188B3D321D6946D691ABD410DC85A44FC3D1628EEBEDA1B7E062AE66F331C701F95FC52ACF82239A223951092C32B88A2EC35D0243F0ACEFC19A17FD6880D76EC6629EDFA589FD6A792D8EA13A28A6116BCFE176A66FE2012A934A2F03E818BBCF31DAF70E93D4FD3A54A44D87809E4EA632705BF9F2FA8809E512269E13755CCC3846D48A42CF417E22E7EB566526D8B3D4587CBC614FB9C05E053EDD5C4BC85F66D930613AD4E7D5E1539A9E60C2C806C72942716E5FB1C8DBFD99D394EC5EE93E660806B2B55C6A45F75BA1F8EEF412012267EC94C1A3BCA1A2A7C71D0BC4DD2B1ECA252781E452F7A832336966E78DA06D2B930B8984A176112A6C202793FADE0360F1D2461926DB28560005BB6C32B18C4C242C7C2385F9B1F6D976DE18B90FD35563E3E8FAF2D80D256BE465E8898F348184B9D01F2F63DEA315AB2515A4F10E647FC53F609DF853D0C513EA15C8C0751AFA2C6D0BD61E8FE151EB73A7176C805859D9D67A70758FC5DDBE6576DC06E00000A25012C3C82A5E352D4F274E55B8ED48CAA611F58B12EA59971C64601AF5922B1F05A9D3E6EC76DA6B3D90C46CE074A68E756F9771A8A1BAF1A41AA1EEDB1724A6F0326CE669503F78533481BCB9184D08B0137EA0D08E0B4DB1532F657EE985EE85568C59BC29A16DC7BF7194E31F4FDBA4A87AAA41C92CB523CBDF2B4A9E18742F45286D5D689169296855B083B36E456165F60579DF514249931268BA162F907759F65E4AA798A7ED47EAA1A50C6983FC9C9630E54514A64E5D3C4F71DB07DC353AE14F619E8D5841123038764511FE3A2CC06DC0CB39994576DD574648A4949300A783C85DA5518CA6A6194A2445741F81FB89F207052878B8864CEEAEDAECAF4617034B038ACBB01A64DA08415AC47A410C68B653ED9718E00BB7AB4A2F244B8A6D8416E9F91FF7C97A67CF2B2B82C94E49D695DB75C6A522241F15A3DE192A1A077212BF921003F6F82218E1C4ADB6C0CC1932BC98901C2B02ABDB662D94F18E1B046BE8B191D544FB242C296ED93617CE349CE8DFB36A551C8B9BB1AC23D16C495999380A597C8BDD66EED95FD24680375870E57285E444EC30D14224CA1E5C45480A5CB945E08CDE41A28B73E12FEBAFFD693A9D012C5619869CFADDA06CD45ABE77A5D68F306DF0B3D785F8D12218954C5EAC2238F7B2308C591BF16AAC23BEB1D9303C0090527D687E5D1AD59CFE20F06C1D43A06981BCF5BA930A1011A9246A79AEBD622A17DC32DDAB4328991CE241E7F3D5F029231CE60A893FBD7ED45A14709753D92507ED7B8BE5E84A1DC2551D519F54B6A213E37B8BE0CD3265CE6991EFCDE7A590EB2EB7C952DB4B8571E7EE343F21559080FBE39F95FE197CAB151C5ACD307CC167E65DE729E907F6849AADDA4C70AD15696EE4074B224AA3330051ED4AA5709F891444EFC85B11CD72E52251706A7DE1555116367CCDD64722CC8E5E7F06C6D1CFCE28FA7554D351B6D1B18D1A486C7A5DE332B57645C2B7C9A9B605100E168511089D3D211C9A01805250365077E00491AD8045800545C17C36CADDCD4E0EAB1E7299F2B9545BD72455534C11013A43142D85B88B8C06C405C5D6DD5F4252E1E1994F6E02B0CE9E112E90FB5B860E628CB0051FB5C4BD9D9E5859D79A99FEB5C4FF747D7BFA9366E969987B255F856AE21855F3D353082BB479EDE001BA36710BECE7C12645772DBA26201CA46716297311DF31164678177EF9268673C7811D1C91E44336BEF343E6AB9CFA82671F44281AF883F7B6EC850302D447919104BE28434110F5291D3FA648795A801EEF8A3EAD2BC0FD624D2D170179CFA0D854F4DE4EF5F3349FAC2B9658D4EC45C7B4A578C256F475B26F43A9B20756E9D081B4D59523274AF4EE8FC7734328A35C388B1338BB59B1BC7D58B88E0A3538232BDCE32467FE8D38637444E4B1F1940632D198E6F44D3876474BA3EEBF5FEBCAE30F9F8E173F0B0EEB2C7F3008B18FCD3B3172EA16774425471162CAC0F41DA7183D05BA2C11D78F757D8AEA9C4A485AF93D67DCD5B7720CEDC1535172E536F635C7CA2D1ECD37F39B33F96B9530B6CE20F1DE982B0DF2342045DE678EB24E30C295DFC57463CF5973BFD9362DF7C65C4BEE247A8E7FB5F12C021BE0CF73635D72F0DC590EACE1EDE651AB5A02DA75DB232C74A9B05FE5F13D79723D6E48CFDCEDF74B66204D16E144592E1AB6A88737A0FA95265A4B3E6294A4E0B60F9907BDBE68E3F68A9A58BE5599CAEC221E9A2C6B58882D15F6A9CAA80952284146F00B8EB7A6026AD96EA6CEDCD3ABAFE6017C9F9BA0670EAED7BFAA4EF7E849758E32F97E901C898C2BD1EC2AA567E1E544EB923D45AD900EC3EB63E512F0024EDBA9ED4B30CF5A6DCB941BCDFB3C77367251B657AB20F62A36B5F057E9E43651DDC950A0C199DC7FB00699C07B154B7E307328D6B3169359A6D7B0F7D872C343C6D2EEB7AB8A379DB93A42D592ABF2701D0B64D7B804BBCC5FFE1B7BD43B3E3C99850864BFFBC6844111AFE69B0F622B7E5A5A7E6E13BE7595728BFE1638C5B0C64C01E0E0AAB9C6250A6AB572B14811465CBDCB953418E1C94971BCC7DDE50FFDA57204D655EC2DE23EBD8341E86DE70C7C2F1D2265D4CC537B3BABADFFC8209C5FED8230F6B3059D0F3EC63971373B7256366E05960DAF1DDF5AC6236E169BC154F473EEA59C257E501E6B61A57B4FB34117340F45DC9520F906C9F7A3630B8C10B153CF836D2D82D2D298E45AD356AD600D382F99CC17426A1480ADB4303B9749A67F535C17D9ACB15AA89B16BEC55577B5785C01B59A9019EE12ADA2C837A38F229D8C5B08463840B638CFE1AD2C1797014B0770EA3BF753B6A31324C3CD2EB0E6B03E06E865B8C1C57E3C87F7C4976DC14EF6383458E56D636FF2D42591F40ADE09117B0DC8EB938FA42292CB24AF54299F3886CAADA860CC15C713EDA1148BD6F32E43EB1B26678C71AD3D26B92899CB22DF0698C8F4CAE9112955945A37F0EB3223BFAB8B26528F7B17599182F5562EEE7C84ED4C524747065155188D61DAF6202A086EDB8D55D9CD19E71C2E45912B065CEDB1105B4F78B663BB61883C4DC28451B0A026DE6441B04BF25E2091BFF1AC08D6C070B17345929EB8AD0B0E7B063C44A2D84927B8CF237915586C196E1967A5BB3CE142B92C835DC4B260009F6A50573354210EEBFEA15FBC95431D92940248D651BB2B3E2C114C7C2877498AD43B4AD014540CF4FA634C0E0F49230D028363804D092672B8BED4FCCE23E59B107FEF8E8D4040515CD1F11A62E4C483C8C2814A8F505BC500E921C44D4C232CE796ECAEF524ABF29E6727194DC1F98865E9F1971AA0CE066CAF860088AA5E0CAB95904BC3D16EE15B044EB13C8AE424C904AA7FA15BC4E1D85C09270D3938FE84412E2B004007ED6C496FE77C1CF721847D3DBE21F4A665AAE72CE164DEEE949C7EE762D73B9191FE45E9F9A778AF8FF33C9DA5789672E43CC77004B757F7A57299DC7B075C0E9D2B583BAD4A700DDA1A08FBE04A74E361B2EC9E39F22E2EC8AD7EEBE9AF75DA5AD5539B8CDA07DB4B767F5B001FEAD13DC63DF16C150F6638AE8237E4411C643E03E3E6F7CC103FED913988CC1C5C814EDC0DEE5242B958AB7B98ACD69F6C15CF020D9F0EAF06D26B3FAC0CD3B9830418BACC995483C88FFE33DC38E81D4A8E7BEF83156AE8A140368E492376FF1535DB42C579E95518F8666863217059C68F9276C76460B2B5157FA32263E4EBD991403CDCB973340C511612DDBF5ACC3DC221B642742F54C995A044B8B4AE4A68945B94040A84CA715A73AA6E4597FB26694DA04C71D73EBC196B6641163030F7902F440E783E709A12475FA3298FF0D472F8B1DED60B169DD6D2AF7CDC54FBF13BA02FD4641B30D6A16EDE32FA661F2722893F8B096530D025395B873B0D1D7D2A058516A09A80BF488FC48A2C557E5FEC2678C18740ED33C5830017884A48988E07F9E2D88BAD87729E42F48CB0CF51F46D7C68D9458C3B4638DED84E20AEFC789361439393F3D4A4150F78CD65ACE2575CA7E8EC12818552192DAC160E80EAEE8AE9971806DDCB30DE4C27EC100AE72A47D6E57236B10959AB7CC8EDCC0AD8E87CBC66F51EB3536D04A4B0825D826B48D88DA79C645C2C8777FD1196B1F14C74336AAED693D5C32792DDD7CC12D57284EA0DD8268D58285F449D1283954868706C497CB5A50A65AEEF172120AA71D0D3B231FF21E938E771820A2EF6DE2E080A962C381649795ADD67A59A1DF2AFE072DF2004BA315DBDBB7D2BD43A5C54D590000AF1281C730CC09BDBC5547A5AC34C63AADE4079AB3C37F9847725503267B4E0060DAEB5BC1317E3D332071C435B7B1ADB5E9635241BD96C90B389A090A9D2E1CCB36C1A95F05E104802812DB0D2C4FBB1188383A49E01C4F7BDC4A014DC5BCCB307A02E69A518C033D9A51BD91E14D9C873AE6D65906F02455E469E72ABCFE9C83C35AE7198B801AEA89951792E7B1D116D1DB92891AF4DF886228F07F4F97397791D5F17ED6ECA288649B022D01AE82ED72A0BC7267BD5C193AFC7DCE203C333EE8F3B5FA9A99ACA40C8354F938CDEB75D02BCBE0D206C7E8AB01DBC0061A75F8A10942946E45AD4A1169B4143B79F2C79A52D9EF6FD6210312E9903E8C90BC4B330D93EBF39AB15F84A43221417D2F449321EF4C02DBB097B282D2271EF5E1EA2FD0A560FD1233E4ECC56A74FE80E82530F31A81C07D992544F458ACF0C25B734596F7BFD3D67E83B61B8C1C387A017A93F6FC36EA52C4A2DB4BFA77E7AF8B22A4FF24DB98C7901F54D182962F0104EABD4E835AB4B0E3A5D1E35500C99353DECE59C2F489AD39BE9BF26BEB018C23C4C72CD3DC48F7972333506DC353D964D2BE02C3819A0AB1E437300DB9BCF1EDDB51CD7408B954A43E1948141DDF865313D7268E0992DE333A9B6504881A80357B1B933CB2FB929E095219449A6261F69DBF42FB94BB323B031592A676282D52DE98E6F214D7690DFF488BD5E5A1D51D204B58639D8423169CCA0B65BE1EDA14CF5F6F8B771811BFB022734526C0AD31DEF077443B9B87580041AA3029E18CADBDB601B97E59683873D58B8BEE4EBEB606165901F812011AD25E8348224FFB8222C6920D5E5350370A1ADA416C29FB8D4A7B5691A278B1A9F084E77A282B66408729D5779AB0B46AC8F7956A548DEEC6F68238355CB70841BFB3CF174EB74B1965CF0C03DB1E3F03426EE4452F8BAC901B030CA88AC5158D6D7089B6A075B51568F23AB02A6294467F3D1633955DB006E82D743E707CDA8F11CB4809972C814D202D95EF2D97D68B1898E82798F0A916C6792BC8317275A235CF4620F5555264C6D84222F47C7AFC02212D509A07DA61A4B2CD89561AA21DA307906E2080DC4E1AC19102A68A41830E50EB813ABA4B797D6882A7B2488F572620577128BB0DC8C34119C87BDAE5CCB45ADDEEE9902D3F3BB5DABA8E266C9A75DC37095DEF3C16D22C20D035DBB61ED424ACC8FD0E560C8B54C0A89BDD4EC0EC2665FE6AABC037B5C68947023EF99992DB2DFAA54583A58AC054D11C498A8BDB12EE0362E755EE32F263C870D0DFD94345C8ACFD911A97F1F6375BED525673559FF6D7B24B3CE12187990AFC66984577A5152A1FB7C6AD0E6AA859D28B21C7F22832CF90D11B9E3260E47D49CD47986C4D6766B66B373385829B0AF24BA66112954E98ABB9612AFB63C39955DFF60BCE30D9DEFDB406F0D53E7496D2F21DF15D72575833D29FE49EB783D162EC5E9AA75BDA48A0B118FA7B224982C7CEC745894693551AA568BF158CB7795A2C9A9D9511D2044F7383576ABE62E005EA1943CEF3269AB2D39FB2AFB80FEFC640C12585D9DDCB154246FF009BC866CD130B9C60D3C10F6C61421EF81C97819CC0F2D18B89781A187688C93BF31AE1F724D0ABE5491E431817DCF4E9C4DF4C2AA035D1FA626B58D321004050A179C28D476A881B5A65BB9AE1851BE710F2065081C4CD41A813EFB692FE4F4180C1ED1F6483D540ECD59CF51406F35A0E00A2D1AB516E150B77778DA214F91A3B1E3FB1D5CF3584D16187776DFDFBB86EF9B9975663C245F38DED3CC6ED095E1F3D32213E9F0F599FBEB4D9108253AC75B3A5575231F07987E7EC5227118929F0DEA11658065FE7D829818688E37D5048ED2249CA46CC9E8881B62D552F76D93BA42887C3AF1DD41F35BD177545C899AE26C8A498C1AAFE04C3238A4ECB2D450279CD47019E9835046F90507E98931D58AD6D960014B72AEAC6196400531E52CA7DDE6D25930FD93E3431A8B9A8DAB9643945055EC37F0D010F565890D0419C7104B285C44969DC05C563B9F8DC94721840A7C8A25CD3B6B4E7F70AF1A33820E721AD8D288AE99B64FEDB035FFC7D876E971371D90AE391632566EFF121BECD938D824638C34E63148CF9BC10B967A6E828D65A8D09B8AAA2187F572A6B498098D66A1A883389C8A7BFAEA0E7A644BDEBF13F2383CB10C63AD70962A05CAAAF04148D8AD22C92A90E02960815B4C2094905A223DE25B08DF8736C9A6841A34C0E1632E6AF0C6043DB6FB2B6AEDCAEEF4C8AAE40B108D2DBA39D497E00CE9D9C92E4741BEE499DC8D7595432E97D9E7A7C69A20AECFFC41E27F3231211960A18CD6A5829F1E2944724BEF85492C4F5507FA4A6B2B6F4482870F207798AC27EFEE936645731B83EF8686E8198266ABEB59D452DC2836B8840C9CA183BCE3B72922182B40D62301CB18C3270CAEE90E19D38E0261102D3693FB12151AA2A8DBABD9D387D70A5EC20F7F6C2FDD1A17B45D32526C68D689DAB0759DEECE65564B40D05BFC200C148939BC2623D9B9E5A4D7A2CA39E20D075378835C1CED222A971D1E296758C7D1A11597F38EAD8448FECE0E3CA2AFD7CD2C625818A9C9B5B0ACE6836538B26C554381E1D558C3DFA3E96314A886484952F0C7526995558846FE621C015D8A04EA082E456195156A82C3CA76FF6C547AC0ABC0DE2843ADDC6369BEE38F8DB908C0E829401825F0B709B13F3295F71AC24BF0D9FED12E87577CC6A3A4A3B5F22B49BF79E899FC110B04B9D4ECBB2C2CCBB79728861DA66B89DAC531CD9B857D12ECD2B5658658C784593D4D91F48E47CB3C3A3997A236AB567F8D0788C1D3B1D0D92738B0D8BFCDCF445C27D48AF5B4D278B03B5AFA6730CAC7705CA6569ADAC5AA07C9129567C659796E7F61CE172810F2D5585E899C2DEA675555B923A093F91F539344BD5E6B2B4561DC71084CA516E500B30BCD9064B305DA9EC37756784854D0218B660AE14CD6EA7443BE4EB567BD4D094403CCE6BE89961E31A1569187D248EBCD8D27955E775B3610BA27436200260B4C7F823E9A8AF2127CD1A731ED2BC590A595F0D6545829A6D10D93B3AA94455D764395AE8B79CC64B3991F706A0348CA401A3398F62017982955081B9D297EB9380E8830A794303F99B0CA28BA5880635FDC899230FF04A7512265E12A5F03B72AB8C606602E36E7420423FE8E822EB5604B71016B510AEC5358B211B22FE79A6EE56CDEB9592DC932FCC7D27B15F4CC2C53EC4F1BF4FC9C1850E34BF119F16FD003A4AB6A531080894006A68773D34FC120AC052332E0D024218DDEA456C22B9D435534D1620F117333E31A60A5D3CC10E28C9B448E9A395612EA6FD79D8632FE9C56F20FA46958F7F0B0A43A4DD35851394CF94A2F30A23BCECA8A0474D322E24007DB1C4AA124078BEC84F89DAE21378C91AC3DAA1B018CC105E2B630F59297D40DC36153A9F6E84C38B0D2F489C836CA7FE2D950BFDA6187012F26D460643EDEF4C2436ED6355C210A5854C0490833A19A722A84001008E51D0A0FE150EF6A57884D3A0DE344E5AA348F03964CBB1A252BDC4B4CF619803C72E645701D1083A7B04210C4545E5293D7BF540609948969C7270FB99A8113F0B64A92212697FCF1B349775F7C25E5A4C271910A777639FB6AFE86441B695153D104A624FDE3453E32AC0ABCC20332560A48CD857890E7B645F9901A47793015AA4DA938330BF58D04B3CC15D009D50DE43A8414146346694E5BB383B86DE2575CAE57EA351589F44023BCAD26555AAB0E2ABFC2CFE5D84A73326EAECE7309EF75DBCAD592FA2776519B7BAFAD4E60D2DF157316EF07C4A4041500CCD892A9750839A29083120EE80022A8EB800C68F279601AD61903C2085D4A63B7A77046C4F4159CCAAC12C868B9F61889EB2AFA62A3C5B4E31393618C7774D5FA16106089ED1526E2CED9B443F079B8D830BA5E3F0211FF8785DFA3D4A97AD200EF7CA04ECC0A43478C770275ECECBDBC971E15E275ECC5E2CBBFB250A01493459DA942B28DD75E22255AEC8D5D799254103250B61141B3445B133BA6EEA2A0561B3DF5CA0857FB76037E2401AE91740856029C2A784889B02EBD40334858679ECCE3C344E09434C07E9ABEFB69522F8D58E6A4E88272F0ADAA04A2B2C766ABDC5E2FE2A640732350D354823F4BEB0DF4A27310A7363B1DB8B63565E9FE6BC53D996C015AE6FC6EB158209F96E69651C3605EB28C51EAB52FE316709CED3A9CC79A0956920E48363C832B409A0A53F250563AE8485D9C077BC014A2F97533260CE1B8091F2FD5347F202C6ED921CA8F6860F420A73C853FAFB90049E7183A2E5C10131F9F3E040F58CD7659823BBE1CAD51C3BA3B200DD6995C48058742068C9722A34AE82B8FAB725C0A0AAAEE72C7285E436D587E35A0C177014F605A4CC47EC5002FABAF469663462F6F212E526C06E369021B10313E05D0F04BBC7DC78B181197C48CD5DCE70350A8D47F5490EFE756EA87C646E45EE0655D338E2033D067259FF70C0F27644E3D53AC378C0C090CDF542F7A0D61688423A7A4FB7293E18328F198DFFA403473B4F5ACAA12DB8C97315B2CBAEAC43E2D730C98DF5F12AC22616E7E04BC27DCE2A9A4B40156C79CC6CD02007030EB002B07A1CB11DB0232777D0FB62799375C5D194F88C39460525EA8362744727F329A94BFE40C1D715CE2B4BA7F60418F6F7306A1BAB61BF0CE237753998162EEE08FB8773103560A8CB701A1C6DDB83D0444A4B123180CE40F6CC93DA04B2B3A77EA340580DE021A409DC52F4C6DD9A27F3C62BD0E1FBCE221FCDE79F0D265F6E8852EA570AAB1C49BD5040B09B5DEC78025D4F6312D67024D9ADDCC0A4078BF35EEE77B1144BC98BC54F579341518C118B5A96F942EA19B796BF240F8C380E7E471F12D2F6E1E6B71A3993A7BD8A8375DA5B176E46D10E5099193498DBD250D025B8F26E5D4268A3B3709937971B2851620AB49E1E758F305C569AD7577325C3DDE9E4787A7647506B3009E4219573FA3840B76FB11463825916532EDE4BB8F99FA555C2933C159463AB50154E51200C25EC81229B574BF5E28311880A32C0D6B381445E8BA55BB2C8AB3A888B89515B2D82C0592BE57A76B162BC8B8F39AEB8C3BDE8D7C582A93FF7011EC4518EA51FBC9F4E7E885F8CDA9A9933B59A0D84E1CBA6FD359DCC5ED8E3B27E3B25C972CC1916CC030B3E80D016FA0781994A77969CA3BBD63CDD7CB2EA3D7745AE9EA15F805132ED10DB01FEFA9A46DD61127F5524DB4DB968B3568FA912788CE58A42611F39D14006C28F5BF75DCA1875B2B0A3D1DA6E93B53BC6740BB0E66928F5B4F1ECD609F6F40DFA2C7B574CDA76D231E8CBF789932D7400E434BC2251D651FD437FA5FD5A8809B068B19A7FADA8D8F1D806AF0A6ADC189E1858A7B5B5583A43AE201FD83AC7D78A05D862C35D06DCCEA0407454BB1F54C51D620D693501466A04769FDF6A4BE54A879C168A40DBB03347EC21A144B0F21B29BADB4F4BD570C7686A9ECCD9E7658FE4787211CE6681F42CA388F9D7BD71E64A97B06F0F9EADD6123C12EBF8FCF6140CDD2EBB9E50361CFE43EFC55850AA423C7086C0B3C1040211F41D0EADC48EC3C85233C432664DF9F60E6F7A62CC6897CCB7A183B52DA2CF33E4F44F2AC03AAF262A9943106B568B011CA18E42F35C503226A1E5753E2FFDBC0F767B938C9742CF9BB3A531E2ECB002D455D23C4522D57856D6279998CD46C86DD2B10D35D36DB0B6210B64F9C0378A5D138C2A791BB600AA2D08824D3718A35A03974A1757F17E1A5D0125E65F683A2A7F7EADF518A4D3434A79DE16F704B025EFA1A466A117F680F51D40752D134A95E2F95C3443BEEC777CC40A09BAA6A8CF0685A11641C58CD74554C4EED28B708C2AFEDD6EA1DAC820417F1B0D4B690F9C1A4AA902BBDBD2E73032BC60E80900AE581F96B14F1E7F491EB25DDA26804C66759420A185CB72F9CF27A7486A5B4D528DA49F1A4C99252242948000FBC9CBF41FF51DF528ED42A0349A7405BEF701D1D7B1755F9BA57C0AC3C30B08CF69616C5D9C4E63DF57CA99104478923C703942C9AF87ECE56F5C7AFFF416D115FA0C831CF564068E402C8AAEC6471325B1C30BABD3230DC01B58891C2E3F2748C37A0DCE53037803F16D07A8ED76B7A68A7DAD38C7452093B0C5CC3A35F34024B3FC56CE2625A954ABCAA8D531F34DDB1DF68AF50889159A309B92022DC152ECAB1023B24FF38AA8E6036FBC6392402B6CE2161805A8F1DCBD0C1B3E911E05A53C914E9C52623DB36258A3181F5A93E24C8704C1FC6EDD876D8591D462CD45E53FE2455E560697B60B721AF86455532C6620BC7DAD89B2B017C21F4058B850340D55229D8D61B7FD28675AE1357C257344851E7BE23B18C8B9407FBBEA0E2F61CAE87B14C3634F7767DE9416B3FBE07DDDAEDD6C694CD5038ED768AF08269C895DC68EC692FF0EF9E89BE2B5E12A910BC5747BBE5B16DA3444171A9DE5BC48F595C2D96F640274809BC3D6C3D30BE5A80A6CC816730A7BEC691C92E948435B5FE0A2662831AEF7305C2BDC36AD63C84D5FC6F234DC90F0F8DB18E5B697E475C4DA6E691BC1B7E4C3E944088166DE59D7E09118A58D5032CDC215B400927FEF9D0781CD1247221F5CA53F55E5111DCFB74BF71E8B0B0B9AFE001C181B8FBFBC0593C2C57B4EC03F92BF1EE812AAD6220B609240917A3100CAFFCC0603C4E62FE10156598D0B7616C0DFB5A23C74A81A71EE63313B93A55ECFC6FF37E22817D1D8F70B112091577A4FE4A59710F0187638D5AE8D1FADB065E6F966F4E2E6E85804679E321C63D7F6AB42391C4A8258DA85BCAFA72BA11C68FAFEAFEE446EB201BC21AAC60BE6F31BD243611E9A67BC84673C34EB78EA47EA285ABFEFAB7425FBFE49E407025F19A6A81F92FFC01762BBDBDD8BCBF8B060047FFBDDD862A51B4B5095F2A8DCD55824BA194B3D8730ABD7144813627495251262FBAEF4110E81EFB85966D6EC2C18B1FAAA37BEFB2C8D137A70C38DCD24F392378BC2636787CA06BCD38900023795492F63AE9FDF06F9D1AD224971D6B5B21F60DEE674829C64828163884CAC6AC2E3301B0873AD554E282054D5FA15FBB193314504613745C321FAD3A1A46EDA901A6D1885384D883BDCD57CB07816ECD4CD735A0F87C63217B42061C06B32AA0348A3D5C6A86B85FDE4632B31E947EA4636C8707DB2B1D9EB7D99F0655692C13E0C4059F79579FCA1AB6C8B5411F8E3E7EE76F404C88E32F0167C2878EB155E8208F0EB21B2820CCD65042BC1248635E0CD3F5E2B31499527D8923A5D9C8589D63B2974C0DA4263C7C4251290B3DE9B84C93C3BA2B35B2F74DA602EB4CA253CA157568A2A6305F2E2B0F6B4A9E67B4D21B14F35C3C4E1996A20CB11DA7D999A5A55A878C3FAC68AA411EB0E90BB2D04C242361DF6E6BA8E1D41BFDA32628491C21FD8A0751DD0B23C5A597C7A8F7718CC996EE198F2603CFAF9C10D3C8A9531959F57105D900242FEF156A4EE811F72A0B3AF64F0F18ADE792163124096A2B8559CEA782AFBD35FBC8A222372153FA98B74A5C6E1211FE985FBD53107ECBA2DC83F720F36535F5F512DE3CF63633E21BE50EBC523E07B58077A69EFE35B85A4C46AF11391952D91ABEF3C03DA4D5B486B33216C4167F22AA792EB42F1A6FE05E4F6CB7BF54B3C533ACA8F6CEC425F2945BD4F1522BC8CFD32524069206F7C3D3F2AA7A4888671D9A170CB0DEF34F0637B03E69B46BCD192C29B69B0FBD2BE4D9264CDEEB6B1DA39AE85782C724F40CF30524C4DE0E46C64C17ECBB39D6F750FB2DD287869DF6E5ED06BAD594771E91C4A18BAEFCE8269BE93582D9D80F0FFE0FE5362C8A81C748CB8D44AAF0E73C8044B500F675AE188DD1028BF2FEA26E259D3CA8AC4E011059FC26C7F288C0F811F64A00D8CA7FDEC1B8AAFF6907191DCDAE60A8CA7D20365EA89734FC78BA14E751586B0C469BD908DBE6A7F962662DA390E2021C0A0051EBBD90C175DF340C724AED2BE0149085E0BC85BCD15F8497EDB93835B714A0BCCD5033B29ADECAE5A0B3C4CFD14661B3B6BB23F4FA50628A1C67831F7875A5A4989EA9FFE33CD039E676420E08295F999DB561C54EFCFDC7CD2C67D29A481FF20E02A73FB3D334A3600CAE78387C882765DBF4BC84D0139F158932F98933E8EF2376A225DA3EF610F971ED4FFF6B4C98428C053CE38AB09E1F4982F13C36A358CCD32A5CF000E07B5D8C8DD4A024C7276136D0D9923D8A8A50AF11495830301EB0C578D62DF9FC890087FDCC58C4FE710FD94E855E48836BCC1A1B13A6CADA5473F4E597BE97B8C1D76B31F7138B1C4FA9700024241809FD846B51936C5B41A86DC66A2A3F537482BB83D38343A0A2AE4811C87B11F471B1B06D3CE872CC70750337229BC6AD850D83C0A0A39EC1C49BCA24F890447BFC80812B3444D0DE7F85366956223DB4E591EE4AB61A36B07D3F80EB2F9623AAB450A0C6F5AE1485E70098AB5427BA9BEEF878AA5220475B8C57E441B5961611C823D2D89FA5C909E61FC940F4AB8A9D8BD2A90BC93D9F4F8EE347114A7ABAF0F0DA52B05E734BD6869253E39A8F052773ACBAEFE9B22613F77565D0360A89A14DBFAD81BC68EE434A67468255FEFAABAE583A8607D57C90217C24EA885F6B27E769705F69B1DCE0B98DD84820F40571A7FB20F6764557DE77DCA9F4BF68A79B30183CDC1EC105CCDE523549B0126E60632381B9639A07DE7A233C15816EDAC9DB8AFEC321239301EE7737302FEB9BAE7AD33B51A78498140735B8DA5C0966570C7BC69DF390765D8AFD02EA17008EFE68F78B092E545BB630ECA69CCCD703E82FBD1A4437228B83680D07D8C11FE3C4F85DF2F7D58A861B1711ED2401D1D62508C73361ACB8973FA8917D9B2AC821C7340B7CA1F499D74D4C353BF51F4528FECFC598A80FA85D3A17B2B750A85C9A5FD669447640CD0C1E00A587B293A2D53AC37EA716AF78573BF9CA57BB76CD6C5C31C5716C7B538186EE3A7F380D021BA79833F214EDA1625DE83055948591CC040171522C98B59C8C8F754D34C1474A6B020CCF1E19FC8583D600726C5C2DEBE06B7D7732AE9C62F47EDBC367D782A41A0C69A507B003A78D6BA394F3B168FB7AA1C119415B6E562F03FB6429642A36866623AADB6DEC41598178E0ABB47A64706E0E9AE5CD1860BED76680D742133393269141BB5935BDF301D2DA507E73ECDE7FCA6A6E60C897371057CD7BC8315D3E5AAF8689128003D610CD0F6A5D696739B8D3055D4C296204CF4814A3D841A48711572E0C40ABDEAF21D382FB3ADA24900E0392F440820AED543AD6DE996A1893B9D35E55359D376257416779C42A5DE5EAB8EA84290FD8EE61B4646E3C37796C2DEF0C91254B27674FD01C26C1AF5BADEA0FDC9C4113E614E68966912E7A5DE0E61EFF96B62D63EC92487B954A4B8B988978924AC8BFE4A9FF4E2BB49FD5B2405AF7746A998C7E5E6DAC45AE52BAFE72F7377553CEEE7652565BAFB077E54C14A0285E06818BC51EAE14AE374C580AB7B1CFD0FCD3CA7B3CB48DB746366433B752807D1A7D7441270D63BD224AB7CDAF9E2CF983F49EEEE30C0D9DD730427EFC7E479E1B0C92E2562A4267B44A2C0A0C59AB1E918DEA13E09250234714AC60DAC300865029543EEC20BFF55A8862B697E6645D12DCFA1A743744290CACCBADA71AAC0AB44290579C57B50DB6B55EDE054B1E95786D75F0B55C60579831C0A74C22AC0D9664684B8784B72413A6294B8EC9882E4AE60729B2721AB647E529662CFAA626AE931995C69E3B4F0A869EB37779BBB2546B55155B9402C3D98C4A8856D7FBE09FAEC53D49C5CB88EC06847A81181F2AB995D948A716176E9160E7A1B662990CFD4D43EF4D9EE7EA8708B4A0F092F5A9F113839D70255B3D10277DB1F71E4C9B84C89B0CF273DB676A73A58E3037BB26AE4596270A23BDBB2B3599416ADD935F2A40A390AA60B3A9B68BDCD5A6B4A6022598EC4D3F57CE76B1160FDF0433CD5CC297B350D926F48DB6DABC3520E2731966954ED517117BB8E557CABE64341C98BB0AD45349A64C0B4C933C1797D13011B291EFCE4CD459B5A429744125E71FAEE214FE5EA51CFA213AB4D7A77937C2E8E16EF385C0D2C450F4A3D929B59A30A66FE1D0448525AB130425D38DAEBB79CF53A1B1A13EF5285F4C6741F505106EA2CA00430B095ACB94F20DC219AF39E0FAFA6C2843E68B47F76BA9321398CFF88A4EA66A59D50038C489DBBA3108E6E529C5B7721B1EF68E43D1BEFD46596E83936ACEDE81F0BC1742411255D1D9DA81286D0F8D989D8901684505082F963BBDCB21614443E230E9D26692A2213772C5F4CE4F1618F66D152C20E20E6923F4B35C0EB22060C6546E221527B762B3E271FFA3D85B2457BA1AFEC537B0D44C228EA48BB7FF2B6F8EC374339784872D380799BAF4FABBE151CDC723756E5C8472F8F47F03D2DACA111304DF5AD67311A0CA80B78C2DF3546E0057F864EC99DB6387ED14AC5FEE336D740C0F7D4FE8066F12F1F10B4A7A241DB752A54F93C23FA42B1C69CA0AA6575BD628A33D52E3A91F13ECB19E8B0F14A823E3727A5DF88A46D533AB6B4819C7C25315314FBCAC7EA6348731B23D9A3AE906502C65A7E4C73475629E61D0A70050445ACE9C6E77DD8F2480414324425B3741FFBED66A7C4EEE4FB920FE7EC9970B95FC36044FBFFFFD391F868843CC786449A8EE6DC6AA6DB988BC241D43ABEAC6580F81095DBDA2A3A663590734982FF164C94DB55F8B80961A6454D538531E0DE03B95840047EB062ECB10FC132252CC947ADB3A51E28A0E032A38A29C145E8A859344A31A3EC581F82AC17C8A2F505AFE58AE2E065D9CE084058F37A4AC0121F264641F55615E7B5C95BFD2241A2B26AFC03EFDA2EC174C7ABEF788611324A9791A096C775571499A219E9C2CC8CE551A71F13CFDC9C93E5D24230AFACB453BB3E3DA6D3F5D7C6B7BD97704C86236639BE4B472D4A4185362B625406EFD98086CE1EACD7125333990C71F55A6E1326A6098DA140BA1A5B206C3BD1F5AC50819CB0196C856B9253126435C387B4572993EEE613CADC45E8A9C81ECCA36728E96489002DF8F042AC5322BC0EEE6D9FADFE75FAEFD8491A8D48F234F451EF6E7DF8FE6DBD5906B0B76EBF52D7CF4B8EF59016FAF9FC248979A9CA5E1A1473E33F8D347224899933A8990481DAD5E0AE73842F0DD48C2A83A46B818A1611F4BBFEC43E52E0D72E2F10292A46D50F7F42F26CBDF7863FF66310A90229D679590E506B787937E3F134F7F81C0E39FCC24DE38549675ACCBF256A9C87C1A5256CBB63DD406B917512C0233F85BC5846E16072CAAFF02C1BCADA31FDAB38402AE76F04E7B821689811FC7D7D1E117641B6CAC5F1C3A9428165CA81A17F2B0836136C7E76695D6C32FA53311873E2C9390D80AA330D92153C63F713E4680742D9D9E3ACFBF3F9C91675675EB1B27952CADC0477D585B1BE28F529400EA1AFD9F2D613958E8E641DDF22EBC70B007A54E3AB4C2D11B5997B73E79D72DD71BD39E8F8D26272751D923CEA0A729018949E6BBEF80C79E6B4DC1FC5D56E2ECCB975CBCED3121E4E28BF531E3E3E68F9965CF522AA57C4BC21BDE1F6836CBC4B8964B464BF3BC01E275F40B9F9FB0610793BFAD34845EC4E1683C3104B4AEBC91D8F180058F0C4835263CB39F5E4677A9180C4973356EC8834E96730FB36D1A97A4E50224EEC84B147FE2C9674BEA915DCC54226F4A8732C3D99D729258C9AC056FDA6A39042DB205C060E668525581D907EA8DC964FC73C3B1746D98A62B2EC8D9690FCBEC7A18D9223858C086D3750DC79CE26FD56C4C1AD24E71EBEC6A579955F27867B81AFD99CBE32342D4EF76C1A0DFE5715C816A9CD05DD1842117A20E9C2170A817AD5AC3CF6530749F0FEFBDC0491F968BEE2B31CB19A7FD50A127579C5DC8501F3D1F35B8F66413212FF3C6BA04321F562A6E95DF07DDCD63761D607DEB1D686912C52E9617D56BCF5CC2D9819A08341BB9CF6E3B2CE944756928E626BC4995A7CD309304E8FB57FB38749A1E8FDE4049C0D3FEC1A511CCDEF389F2A93C7764EF63955B011468EBFC859FDC06B1D61B28ACDAC76D2084E60893D804FA15878E46ED0A01CD25E119801013C2ACF866C578E0C25C35A2DF9F5AABA5CAFEEDB2F42392D81A594C5E74D0C6768B756A1CE814CB66852F65431C169B65DEAE9D5730538BCB5DB06CCCD0764B9C02AE9E71489D7E9C3B89D6050FE1B8CECF6832D57D5206D2BD5634EFFF5E601EAA339E9100337F4CA8C2943E660950BECAD7665797C3ADD2082654FEC40F19BABDEEA240F0B5BF28FA9FD33A837DB1F63B5B8E6CD9E2D6A9384CA2500175E6562C890A4672C214B966F6367A1158FEBC0EF9614427085FD5A94E4B4B5EDEAD9C8EC7DC39B6CDE22077EA3D348CD6EEDF5F9D24CEFFE45C3401A14B554DEE96C1134256455A88663F946853AA16123B80A9E7790BC692D9A84FEC2724977EA817C9191E3A27610855AEBB0588FA438924B048F451B7AD1E108B0B5566DF14F837423226AC170B299D8049094510A3876F5668D9CA7FCEEF16B47F5463D534D69DAFBCBE0F84B05395F94725DE5F57CA197DAD7C6B6088726DA7135FF32883709332F36BFAD1F2C7016228FACE309A8990869935672FE9BEF533199AFEF2E5C62B77600A44DAD559AD222BFF653943B7675C67595D055632DB5EEC077A9E0592A78ACC458859A699E8546ACCB73BEFC2ECE5F9DC12D825F1A621F1A6EF470A632595D579ABB1F10A9704453B7E1732B02BB2272D094E8A8C40B10FF57DD1D036A681D905BC6E0230C141C16307037B80437C93555DBAC68E8B5BD770BC0C58A288FAF84490867F9E0E53D79EAF2D1CD91936495D315DB41D0A887201B6316D652BB6FCBD9040CECED4AA7347B745CD79F89D453216CE63DAD8FFD3216A8219623D3B5CFFD75D48BE1CC73BDB134134F16C06DD64B086D430B0D21B243415ABA4A64B8468694F546135B86358DAF2247BFFE4C370FC8CDD9A0377825DD8BF46FCB606428FD7F3B92D8180AC4E72CA3AAF6BA668A575DCF80F9D1D6B2E50226E1422C67E034603ACF5D852A31934D83CD9955D2E7BFAAD625CB3B457DAE63789367AEE3342D76014B61123CCEDF9A33B1224960FC57D3539B655623070C6244B4B1721B446D8F0A59481907DC3AE105D8C3AE1265003511349A5CD40FAE963A918D6A48D2459692D90E13026BA1519B7E7E550E014AED23BB8C5E05754C75C4C969A9ADF0871C90C8CB3328272DBF4EA927CF320422CD0CFADDADA650ED5DACE3D598276277CF0333AB89282A472ED65F0BF1544ADD2F2C5FAF5375242FC6ECDC52FD7AA519009FD14AB6D8F56F393A94ADC2281DAB508AAD392CAF186A895A92083C3EEB6C1C6659B11846B3D1DE7534AC6BA1A334A548E80C427B015F7777EF25F2FAAF22A9E6D71919C437794871AF7B81BDB8951F4CA697CC3450E98A6FEB81C85DD549F318D224658BEB399A0FC1A486EE459808C8816EE45B98B0395FC15369CED2557F02D2BF5AC21F6182C5CEA1D8788A7238F9D644B17F83EF1C04BE10B0BCE1E8C5C9AD40ECDCB16E87526E0D0E974BDEAEFAD05BAE89B56C225DA0EE4519ABC1CAD6D2BEA96865D15CF6DC82E1E59294C60B536821C56B863FA53A3169E5113CDA5C585F05FB48E0C44D4032E077413733EE8D53D46C06255B8B9BC220D0192B71CA19BF08AA6AA0C70752A8B29DC5C6340E5BB476A0F4238A84C0FEDE4917B189B921EA5F8CF782D771826CBA44E4818AFADF2AE9BBCE5BEB595576F407173BC34051F687C31905193E33CBF4899075BC5B0F0B8FF8DFA281E074680BDB351F68BD80F02F5FA22CBEABA16892E07C4359F7ABCF60DC38ED8C9CBC20D04C886A48497A12B1C42F90B25C68958F86CDF3843FCDAD2F91118DC38C404A22E94C6117B2C01748EC9081B91385C596F61F69C5DD827E004EAABAA13711F84668A77AA5F21E823D0C421EEC9C9E249BE2FEAD6126891DA6DD016287FC01EFCC78745A6494B6AE7987DCA9CDABDF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BC"));
+        byte[] sk = Hex.decode(
+            "0FF9050C0802FF2102060B0206FDF9F8FEF813EB0A0AFB01040CF80403FFFB05F8F70603F7010DFAF4FB080700FF0B07FDFE0E020407FAFA040107FCFCFB0CF700FE0AFB02080AEE00150E00FDFDF00AF10206F9FDFA0A02010FFBFD070103FEFCF9FAF50D0402F7F204040707FDFAF50419FFFF030609F9FA06FAFB0CF10AFAFCFFFCF7F102F400FD01FB0803FDFD040A08EDFB0AFB030C01F8FFFB09FA0A01FDFB0C080807F6F410FF050A01F60A0C100504FBF70DFE02FB110DF400EFF301FF11F5040EFAF71107F70000F20DF0EE030109050C0909000009F60FFFFD10F808FDF7FB06FFF806EE0900F800F908F50302FFEBFE19000201060303FE0B0C03FBFD04050405FF000BEF110506010105F901FFF8F50AF802FC0A0406F7FBF903000E05F60D0703F9FCFCF60D00F804FFF603100B0810010E04F1F3F3FCF60C01010DF7070C07050309FB010A0C05FFF9F8FA05F9FC070B140609F5FFFEF3F9FF01030004FB02FCFE070CFA02F00BFFF70FF8090B04030604FD02F9060506120C0A0000FFED1106FFFE0500FEF9FBFD0306FA070D03FB020403060C111008020BFBFFF6FBF0F9FFF700000200FD1A040C03060B04EEF701F90BFE04030AF80303F8FEFEEBEA04F706FD0CFEF8FE0D02F2020209050213040405FDFAFC000AFC0CF5F70D080D0806FF0113EDFBF4FF0CF7FDFE0104071107F2060401EAE902F80B01FC0AF4FD0008F7F2FEF8E9FDF005FC070508F005F508F7FAF6010810FA0CED120200EB04FAF6FCF7070910FF0607F70800FCFA05F800000BFD050406F2000407F504070200EFFB06F107F00C0201080F0406F5FF080CFAFCF6F6000B04FAFCFFF7F8F60D00040AF1FD09F904F9050003F405FE02000308FBF008F80DFB04EF0FFB07FFF20A0BF30EF5FEFB07FD1009F40D040608FC0EF5120D0D0DFA0805020005090B03FD03010709F6010CFE06EFFAFEF4F103F40D03FBF7F90EF6FB01F50B0AF700FD0703FA00FC0C01F606F9F1FF0A12030801FD06F70A0BF8FC0008F6FE0701F909010F0C0902F900F6FE09F70102F80CF109090511FC05F104FB0704000607FCFFF0F40205FF070C0AF411ED0C040C06F8FD05F1F706FEFF01FC04040DFE07000203F50004EDF6F9FD03FE0B1102F301FE10F70406F803FEFBF9FCFA000C0B0B071509F4FDFF07F4FFF101F6FD0CFF01F7FBF703FB0503FF01F4FCF1F8F208080101FF03FF01F5F311FD010102FCFC00FDF5E5F1FCF806FC01FFFFF80D0105FB00FEF5F105FC07F80101FB13020006FFFBF40501FBFA081201FBFFFF0FF1F404F904F307070706000CFCFC0509FBF905FD0900FF0C06FC070010FCFFFB01FFFD05EFFDFFF504FD00FAFD10100900F3FE0D03080B0006FE01040CF5F8F100F70EFB0101F2090E020C0903000104F3010901FAEBFDF8F80002FEFBFF02FCF31100FDEE0AF8FB06FDF901000309FE040E08FDEFFC02F0F8F108F3EC09F60506FF030400090BF907F608F301F4090B030A000A00FA0904FA03F100F9FCFAF2F70BFA03FCE8FE010BFFFC000205090A08020AF7F5F90AFFFCFB0501F6F809080201F30502FA011103F3FE0604F3F5F2F9FF0BF9F5FE06F7F90BF70003F312FFF30AF90A0100F9FBFDFA09FAFF0CF3FBFE08FF040705F60400FC05FAFF03040913160408120700FAFFFFFEFF03F602F5FEF20505FAF805FB09FB0AFB03F2040404FFF6ED0506F0FFF803F8FE06F8FE06F9F8F602010AFF0804070FFFF8F8040105FFFDF900FEFB0DFDF5FA0BFCFBFCFF0501020B0CF9060AFB09EE03090209FFFC07FC0208F7030A0F0812FB030FF900FE021007000905FF01030AFCF7FAF3FDF504F504FEFCFDF608F4FCFC03FAFAF3FD03FEFCFB01FC0BFB03F8060A04FD0406F404F51308041203FB0002F4FC0D17FDFD090C0507FF07FD070902FAFB02F70507090A000107020EFBFA06FDFE00FD1101F80106FBF70803FEF9FBF90007F804F812000C0AFEF403F4141002FB0A04110001FD04080EFA0B0102EE050B1005F90A130DF90D0AF708F704FFFAF50103F4F8FCFB06070006F70BF800FC06F7F3F3030FFF01F400F5FFF914FE010104010805EE0309F4FBFFFEFD07FD0811F6F7000A010207F3F3F8FF0FFE0208F8F6FCFDFEFC07F40200FF1A11F70600F60E090AFB020309FC000CFC020205FB05FC0905FE03EEFB060B04FF00F5F603FBFAFCF901FEFB0BEC04FD0B03FE000203FF0DFC07040500FE030AFD0DFFF407F908090908FBFC03FB0BFC09FAEF0100010101030B0DEEFCF3F6F810030D0C04F4FA070801F10FF40700FDFFF90604FF0A0203F4FDFA070601F7F5ECFE00FEFFFEFA00FB04F9F5020904EC03070A000B05FB050CF1EFF5F6FBFBFFFDFEF202060BFDF004F80AFCFEFF0202F90C0104F0F70BFC020702F3F9FF010700FD03F700F7F5FF02FF0AF7FDFBF4FCF705FE0CFE00F903FD0300F802FA03010CFC06FCF003080508FD06F90C02F809020007F9F2F00C020EF3000D07F601FCFDFD0EF70CFF0204FA02FDFF05070A0004FC00FBF30203F7F4EDFEFF0302FAF2F2F1F9040102F9FCFFFA01FA020512FA0DF90D00F2F604FCF3FFF3FCFC0602FCF50B0304FC06FDFDFB0DF801F5FAF90AFEF708FF00F7FA1100FDF71700000AFBF7F80AF507FF040302F80A1206F70400FE02FCFF00040A1004ED05F8FE0BFD0900F7F201FF0C0B08FEF8F2F4FFF50F0212FFFFFC04FFF80CF6000305F909020EFE00FFF605E8FF00ED0C0BF404F5FAF1FC090D07F10B000AFC08FE15F900FB1005F2FE05F7FEFFFCF90FFD0303080001FFFFF611F90BF1FC0E01FEF4FC0405FE0206FC0BF803F90B01010B0A0DF8FFFBF605F8F009EBFF15EEF1110C03F8F6050DFF1A030EFF10090C00F0F5FAFDF2F6FA0005F707FEEF05FD030AFD0AFE09F8FF02FF09F8EDFEF50001FF03FEF10310EEFEF7FD03F504020FF808FD090E0B0FF904F602F7FCFB09F6FBFD031205FEF5F802FB060801F6FAFA090804FB010CF4FEFB0CF9F3060308F608FDFCFE0A040B02FF0805F3030E040CF9F904FA0FFB00000B01F90705F2F6F007FF03F801FF010F04FEF50011F8F30AFFF10BF90AFEFAF5EDFAF50304FA0CEBFF1204FF11FCFC0909F40006030402FC05FF0803FF0B0106F903FF03F307020AE9F5010103FC02F5F60009FDF203FDF708070201F0FBF5F9F50605FEF90BF4ED03F9FAF8FF02F2FAF0FC0301020E000BF50007FE0C00F8020107FB0008F8F50500050CFAF90C01FB02F1FCFAFCFD040615FE04F90819FF0004F30104FE10FB03F1FE0EFFF9FCF8FA0509FB0704000908070211FA06FD00F6F803FBF6FD15F704F9FD04F5F7FE0708FAFE02000EF31AFF08000101F70D081203FEF90002010209F4110506FC0A06FCFE0401F5F80D0907030D01FAFC080FF1F1FAFF030B0210FC0202FAFFFB0514FAF706FCF50208EDF8FD0304F6F901FE0907F4F80FFD0A07F606FC0C001316020A03080202F20008020709F9FB030207FB02F802FA05F10408FCED01020FFEFA02FD0414FCF5091107F2FD08F910F601F4FF02060AFAFC0204FEEEFC000008FBF7FA0805FCFD04FB10FC110100FA08FB08F3FAF8F1010309FDEF020102FEFA03FEF9FEF708FFF6FC09F8F5000D0BFEEDF5F40302F9FE00FA00FF02F5F106FB04F60CFC02F9FCF8F90DF3F8FE130408FF03FFFF0102F7F5FAFA0DFB04E7160DFEF004FCF415FF010BEF01FB0AF6010305F3FC04FDFDF614FB09010807FD06F9FBFA090DF6FAF50BF4FF01E706FD09F401F80701FFFFFDFFFFFEEBFDFA050602FFFC050F020302FD01FDF9FA0505E5FB08110614FAF80102FA0607EEFC05F7EF030A01FE05FC02FBF8FEFAFEF5FFF606FE07FB0001FBF1FEF804F5FBFEF2FD0303FEF90303F2FD06EFFDFEF9FFF0FDFF02F6FF040302FB0BFA0EFA0FF70FF8F8FB00F90BFFEFFF0704010CED010302020005FB0806100409FD010BF607F6FEF5FC0714F3F8F90005F7F80702F10301FBF80BFAF20509FEFDF8FB0FF9FD0AF9F6070A040E0B08EE03FEFB0CF2FE07050002F8021005F6F215FFFFF9000DFC0E02FF040602FCFCFCF502FC0AF4F911FBFE0D07F505FBFC0313EF030BFE07F503F30BF2F315F9020202F204070103FD0903F3000D00FF01FFF5FC0AFB06F5FEF20405FFF602F201FE0001F3FC07FB04FE08F8080A0AF3FDF9FCF9EC04F9FD00FB04F003F30EFEF7000E000100FB090A070606FEFBF7060BFCFA05FBFBF8FFFBFFFA0A090BFFFDFBFF0402FF0103FDFBF807010400F201F8F20606F6010801FD0A120B06FFFC03FD030FFDFDFDF40310F5F606FE0207040304E505EEFDF20205021AF8EEF50A0003FBFE03FD04000AF601F1F502020FF600FC060CFF070107F60210FAFE010404F109F7FFF7FCFB0C040BFD0BFAFB02FB06FEFB020DFB13FA02FA07F10308FD0200FFF6FFF7090504EAF80308F5FCFB040B02F8090AFA01F9FF05F9F4F8F6060701040404F9FE000302030DFA060503FF0002ED0904120706FE02FCF808FF00FEF2F10216FC0609000BFF1001FAF8FEF40A000103FCFB07FDF6FFFC0EF803F706FFFC10000509080000FA0703F60E00FA010303F90203F1F9FEFC0BFBF902FDFE05FC0313FA0505EF0203F8FFFDFD02F4FC05FF0601010202F503F1F9FEFA080AE91407030A0407090AFAFF130DF803FB050AFC06F300030DFB0607FB04FFF805F50C05FAF302FDF3FEFD06FD080DFF09FD05FD0DFE030508F20102F9FC02FCF40A0C07FC04FAFDFD08F10DF1FF0AFAF8F7F305000F091205FE060501FB02010B040F0AF5FDFE000B00FFFD08F1F90AFC02FC0000F9F4FDFB08FFFEFCFC01FB070106FBFC100FFA0B1102010E0504FE0109010700000507F904050604F50813F2060403030F03FCFE020AFF00F30AF5FBFAF8010200FBFEF9FB03FD0505FBFBFE090B04FB02F3FE0403FCFCEE07000807FFE80BEFFD09F900FCF904FB01FF07FCFB0500F5F8F602F90902000B0400F2FC0101FEFCF1051B00080C0307070702F9FBFC000B07FDFD0AFDF4FF07FA08FDFE07F6FEFEEB08F7020006FE03010B07F1F6FEFEF9ECF4F6FBF905F7F900EE03FEE80EF9FAF5F8ECFD02010F00FE0EF106F9FF0CFF0C1301050CF4F5FC01FFFA09EFFC0500FEFB00FE060404F40906F7F608F70506FD0010F8FD17FA12FD06F2FAF6FDF802FF060B0FFE01EFF5EEF8FF0403FEF00902F5F7F9FCFB0605050DFF020FFD0D0AFB0A13020103F30CFBEE02FF040AF30BFFFB01F70408FAFEF9F4F9010608F50000F101F8EFFDE5140600F8FCFA03F5F2FCFC040403071002F8F20600FD0509030702FD0CF4F90901F1050107F604F40509F704FB07110304FEFDFF071004F70D0B0D01FE00F6FFF706080A06F9FB000DF1F10300FD08FEFEFA0DF905FFF5FF05F80E01F808FE04F8EE02FB040505F60D04FDFD05FD01FC0511F70CFEFBF0050C0503FBFEFAFC1005F707F9F80FF400FDFCFB0105F901F3FCFE01F5010AFFFEFD0CF9EEF7F5FAFA06FFFA04FE0FFE08FAF1F4F70AF4050101FF000404FCFD02FEFA02FAEDFC08040AFD0DF3FFF9F5F2EE04FCF70306EFF3FCFB0505FFF907FE00F803060206E8FD00FF00FDFDF9FCF60707FB01FBF8F70A070308FE1004FC050DFD0AF808050DFC0BFD06FB03F3F5FA0909FBFB02F802FB0AF810FBEAFDFBF6F20305FAFAFC01040B0AF7010A03FFF9FBF8FEFFF1FBEBFAFAFEF00604F800F3020BF9F8F3FC0302070B0AF70603F7F001F3F70204F703FD0AFCFCF506F4000A0AF003F103FCF2FB09FCF9FF05FEFF03F70507F105FC13050305F6090C06FD000A00FBFC0C03F9FCFDFB03F60C10F5F80601FF00FC050BF319F914F4FB13FB0D0EFFF51007F2EA05FB0603FC00FDFCFE0700FFF9FE081102F90C0201F50B03F9F4090EF809FF0105F50C0603F9FBFDF3011001F8FD080B04FB0301F400F2EF020302FDF80202EDFDFD000E03FDFCFE0CFF0AFE080A080202F902F1FA05FB05FCFCFCFFFB01FCF9F30508FBFE08F50B040B060000FEFFFC05FB000FF8070102FF01F7F6FFF405F913FF050A04FC0BFB0203F6060C08030BF3FC04FF0AFB00FCFC0008FFFEF50C0102FD06F00CF903FB020104F504F4071204FBFF05040A0CFD09F70106F9FD000400FF021603F6FF020101FCFC060004060108040EF1FAEC050207FA0002040303F5F7F80806FF0803F40505EB0DF6F9F703FCFB010A04FAFB01FBF50607030602FDFCEE031106FB09FE02FEF302F80103FD0D040114FCFD02F8FDF6FDF9130DFCFEF40205EEF6F9FF06FCF8F8FF12F906110DFAFE0BFF03050CEF02F7F6F9010204FB05F61006000704FAF90B0CFBF9FFF2030502060AFEFFF5FAF814F70109F010F70408050501FF07F0061108FA0FF6F7FA00FC1004FAEF06FD05050A0302F90E0FEFFAF701FAFCFCFB07F3F70209FB0406FC05FE0006FDF6FBFD0DFBE70A08EEF50F1004F5FD04FCF90DF402F4F6FF0905FBFA0CEF0116F50FFC0B000BF801020501040313F1F8F70AE80D09FD06FCFF0AFBFD0713FF040200FF0AFE000106F3F804020403F2F9000B0006EDF90900FB080705FC0801FAFFFFFC0F0709FE0408060201FEFD02FE04FA0210FCF60105F8FCFFFD0AF91AF40DFB0E00060708FA02F8F40A00FBFE08FC0C070802F201F9F2010114FBEF0A04FAFDF8F80502FFFF09050BEEF6FF0E08FF00FCF7000B010FFC000401FE05F00117000C06060902FF0400FFF6FC02040AFEFAF2040506F2FAFEFBF3F703FAFC000404FDF60603F8F9FF0E08F402EC0108FAFC04010102F606020BFBFDFF00F601F4FDFBFD070C05FDFF0FF6F8000001F50B0EEFF8FAFA10080A0EEAFEF1F8030905FB0A07FC0C0FF6F4F909FB110FFC05F106040103FEF50D09FF05EC07ED18FF0408F3F7FE0402FC0BFB040002F5EA0AF6F6FC06FF0AF3FD06F3EEFCF4FFF7020C0AFDF204EDFEF6F8020CFD0009F800FFFAF5050AEDFB01080BFB0509090F02FA05FD03F0FE0C0501FEFA08FBEF01FA01FF11F8F5FDFA0501011600F7F905FCFEF9F6090D02FF06F9FAF502FBFA04FD0AFDFB0FFFFDFFFE04FFF2F8F2F40C040AFCFD050CFAF80EFE020E03FFEBF607F807030BF8FF11FF10FD020000F8FFFBF8F21B060506EF0400FDFB09F0F807EC0207F4FE03FBFD01FA050903F907EF04FEF4000A040204F3F00203FF0C0006FCFF01040D08FC0EF2070004FAFBFCF9FCF801FEECF6FAFC020A0A010403000202FD1701FF04F1FE00F1F9F50202F3F5FCFA090AFDFCFAF1F208000407F007FF02FCF3EA0CED0904FB0403FDF6F3FAFCFDEDFF110406F8FCFBFF0503F8F208FAFE020C0100040BFDF406060E07FD03FFFD03FEFD14FC01010106F8FCF4F7EC010309F20514F3F6FD04ECFDFD0D08FEF7FB0302FAFA0406FEFC0D05FCFC010808FF02090C0403FA0003000D00FEFA070603FB0100080B14FD08FC05F50506F7FB010504FA06090209F7FDFE01FF01F4040AF80A06FE000204FCFA010A040003FCF303FF04F207F40BFBF8F905FC01FEFEFDF7FAFFF6F5F609FAFDFFF009F3010EFF09F3FC080205F6FF060A010DFE00F7060A0A0E0401100907F5FB000505FCFAFA05F504FF010D0606090AF7050008FB010808FB00030AF1FEF706F3FEF50B0215070A0009F7FE01F90A0604F9070E0107EEFE07FFF6FB060F0809FFFC00FF080F04EF040A0B00030F0302F3FE0DFCFC08FD01FE00FEF70804FE060D0409F6090405FDF1F7F8F9FD050AF9040F070DF7F5FF03F4060802F7FDF9FAF8FE01FBFEFB0D00F8FE010008FAFAF3FAF703FA0DFFF507FD0AF50305FE000101F3EB0801FDFF0DFCECFBFCFE10FE0CF3F9F6FF020AF6F408FE02F904FE010803070208F8F5F50BF0FE05F8FEFEFEFC0FFBF0FB120408FCF8F9FD020901F10602F5000CFE00FF13FDF0F101FDFEF0FA060F00FB0CF9F9ECEEF70901FDFB03F2070605F7F4FBE70405F8010402010AF6F411FD0CFFF4FDFF05FA01050904FBEFF1FA0607FAFE0704FC00F40107F3FC08F8F90BFEFFFCFB0CFFFEF9F3EBFDEFF51408FDFE0F000603F6160C01FF0110FEFC1502F2F5030EF1F6FDF3000DFEFCF6FEFEF90601F8FA0C06F8F6FAF9F708FEFEFDEB00FC04F3FF010312F90105FCFC04FD02000801070AFBF80DFEFFFF090203F6F40B020B0700FFFEF910FE1304FE07FCF808F6050500FFF9F903F90C0809F6040E16F605F7F0FAFA01F804F6FB050F080705FF03000B0A01F40C00F9FE0BFF0605F80A08ED09F6FFF60BFAF1080AFDFB0A0111F9F20BF60303FB07F7F303FAFB020C0212F804FCFD0AFBFF090405FC06FE0702F7070200FBFCEE01F90A06F01304FBFEFA04020DFA07FD0210010304FDFC020BFEFAFDF3FF000002FAF3F807F9070EF903F20E02FE0104000702F9EF060C01040C0CF8020407FD02010BF90704FFF7F8FA010D010602FD0D00F7FDFAFB10F00100020B01F708050EF6FDF0FAFAF90DF909FE07FCF7F7FBFB090201EA00090206FBF805FCFFF80906FDFA04FF00F3F4FDFC0607FC02F409FDF7F80709FC08FEFB0901FE0B021002F3F4FFFDFCF50407F70101F801FAFF08FEF7FDFFFB0B0905020705FEF5FEFC01F10605FEFEFDFCFF0EFEF302010B0D0101F405FFFE040002FCFF040804FC01F80805F7E904FFFA0905F2FAFBF8FE0509010E0702F2FAF8030AF6F2F605080B00001102FB060DFCF5FD0709FF0000FB030908FFF9FA08FF10F90CFCFEF804FD03FC06FF01020405F9FBF7FFF206F80203FEFDFBFEFCF0FF1000030705FC06FCFBFAF2EC0102F8FC03FD01FC0601020CFEF307F8ED0DFF0703FE0DF7FF070C0904FD000E0305040909FDF9F7FCFF07080EFEFA0800F600F6FC0001F803F9ED01F6010505FCF8FC01F7040208FDF2FA0107080609F3FEF7FA020AF512FF1601FEFEFE04FF11060A090A0008FFF9020909F3FDFF0B0C02EF09FE08060D0CFA0909FDF6FE07FD0805F7F2F6F8F6FDF70402EE0F070601F901F6F7F505F701FB0703F0FEFE040304FC0EFA05FFFAFD06FCFB00F4FDF9F6FAFEF9F9FCFBFD0A0108FA09F9050B03FAFF020600090A050012FA0FFEFB0E02FFF1FE0F0A0CF3E5F90802F6F4F5060008F9FAFF0706F00A0E0507F6FB0BFBF5FAF6F60015F80DFAFEF20601FEF0030DF800FF0CF000090C02FEFB04F9FFF6FDFDFB030EF507040308FEEF060604FCFC010AF6FFF414FA000301F8030807FEFAFE110002FEF2ED0301FC050306FD0303FF06FB060A0103FBF705F7FEFF07060C0B0801FC020DFC09F4F909FC010A08000B000C0E050603090AF80209FD14F70BFCFA0100EFED0705F9F308070708ED0CFBFE0C01FC00000BFDF7FF0E000BFAF703FDFEF00A06E5F502060008FEEDF7F7FFFDFEFE04FB070213F6FAFA0206FEF0FC0D03FB02F803031107050403FE02FEF90E0118F00301F6F102FBEFFDEFFBF1FA060F07F70C02F70C04F80101F70207F70002FC010FF6F8F50CF30007FB080C03FE04FA07F9FF02FC030CF500FF1D07E6FB04070CF5F0FC0207FA03030304FD07F2FEFA081002010502FCFF02FB0BF2FF000DFD0003070F0602070B0200F8FCF2F905F9FC00F8EDFDF901F102FE01030B040F0306F9F411070BFAFCFC0805FAFD00F90102070909FD0BF9F8FB12F7F708010FF9F904FD01FCFE0308FFFB0709FCEC0BFF0DFB0F06F5F7FD01F9FBFF0809FA040A100DFFF7FF0204F00206FE04FD09F911F9E70700FC020BF100F5090607FDF505F90AFD08F8FD0908F8F806050D0503FEF20104040BFD01EC01FDF50A050807F105FE020103080606EEF30302FFFDF50E0A0805000513FE0207F400061100FBFF0503F7F6FEF8F40FFF010903F7F9FDF8F6FE0903F101F9FDF9F9FE05F6FF050108FDFC0405FD0F06F3ED01EDFE0AFBFE01FEFD0AFE000405FCFE0407FBFE15FDEDF2FEFBFF06FE09FF05050707FF0BFFF9FFFE04020307FA0900FB04FBFB0A00F10701FF01FDFBF90108F8FE030AFDF8F7F90902F803F5FDF9FE0C0AFF03F1F909FAFF00080FFB07F603FBF404FBF3FC0E0AF7F803FCFE0D020C010B02ECF8F905FF07F5FDFC000CFA0FFD08F3FA0B0007F40B070A00F70405FCF90B08F5F9010C0C00F8FC1C02F9FCF20B000AFCF9F8FFFE0101F1050909F80AFBF4FE01130D02060307FD0E05FA09F603F807F40012F10709F9F6F90903EC05F9FDFC02F9F0FE02FB0003090306FCFF0BFAFFF4F4050F0A06F50106F7F7FD0A08FFFCFA06ECF50105F9001015F7FDF901FCFF1111F4F40204F1F6FEFEFB05FCFC0E02FBF909FDF90BF9F6EE13FE02F9FBE60507FBEE0DFE0704FC0106F20300FD0A0B05FDF208FAF505FFFD00F90506000403FE0A0113FE080202FB010202FB0A0FF400FD01F8F7FDEEF50E03FD1AFFF8F40202EF0F05F9F810041602FC03FDFEF9FB0205FCFBFF0E0701060BFD0B040A01FB00FEFD0113FFFDF3F70C02FAF2020108FD0F080307ECFFFB0604FFFF0600030005F30A06F70F05FA02F60601F3FFFD0A0CF509F20010090207FD08FAFC06F5090004FD070F040D0C0AFDF404F8FB0003FF03F3F80216F801070F0AFDF7F6FFFD07F9010204EE00FDFD1309F0F5F905F8F900F714EDFAF0FF01041203FE0802FCFD02F7FCF9F8F508FEFAFA02F6FC0606FC04F2F8E90307F4040D0102FFEDF6FFFB0907F8F9FCFCF7FAFA0606FB0F02FD0FFFFC0E0A03080C060EFAF8FB00F60102FD06FF02F805F4FB040BF402FF01FAF702FF0500F800020BF805F90203F4020108FB01EDF8F808040AFFFDF9FB0809F7FB00F8FD07FFFF07F50AFC0309FFFBF7FCFEFCF9FEFEFBF706FA0A050906080702FF07F509FCF70304FDF5FEFAFE08FE0502F707FB080005FCE90C0BFD040AFEFB13FB0709F808090E03FF040B010203F5001AEE01FD130A05FD08090E080007FAFC1204060E0103010000FEFD0807FCF616FC05FE070906F906F310FAFF0311FCF9F3F304FFFD0003060300FAFEFEFB04FA08F40E04FCFD06FC00F8FBFC15FDF2F8FC000FF70DFF0BFCFDF209FCED06F908F6F8FDF902F503FA110CF806FFFB0CF3F706FCFA00FD090C000AFB051203F803FFFD06FF0D02F211F709F70AFB0303FFFDF4FD04F605FEF504EFF210FB08020B05F60D09FDEF09F5FCFAFB040503F902040B04000C02F704EA03FB0103EDF1F90EFDF503FAFFF4F6050BFBFBEAFE0AF3FCF4FDF600EE0AF80412FAFEFAFB010CFCF905FA080AF60CFE010F19FFEA13F8010500FFFDFAF2060002F9FFFB09FEF501040AFE10FF0508F700FE1606F8FC000806FFF3F7F600F60209FD00F7F9FE0604F5F5F2FEFAFA0204F9F30F0FFDED020405FCFDE905FBF0F603050C080D01F51304000E07FBF4F7FC0308FD0001FC06F2EDFFFE010AFCFCF4E9FD0106FBFA0A0202F804000608FC12030703FFFFF8F9040D08FCF8FF010004FF0A0306F804FE100510FF04FB01FC02F9FDFE03FBF3F4FD0405080CF9FF0A07FD07020407F904F4F3040A03000E0306FBF70108050EFF060FFDF8FCFCF6F80B00F9FC02FD0801ED02FC01FAFC0E0404F0FD16FE02F9FF07FA090701FE08F309F1F90004FE090A05040014FB0C090A0FFFFB0AF90D06F9FAEDFA00120102F304F8EF09F6FEFF01FB0600FB03F601FDFDFCFFF5FF00F80201F90502FB08F707F5FFFE070BFE04FCF609030107070E14FB11140204F7F8FA001008FCF5F7F207F70EF700FA04F1FC05FBF6FCF40CF209050AF905FC0CFE0B05050AFF0605F6F707F70107F907FE001004FEFC00EE09F3F5F3F9FD04F9F509F5FEFFF8FBF308F2FDFA1BF70407EEF60D07F503FB04F705EE0DFF0504E91708060605F8FC0E06F40AEE09090A0AFD03FB050F0907F6FD04FA020901FEF6070003EBF50500EF0200F3000002EB06FBFD07010605FCFA0FF508FC060308F5F405F7FAFFFBF1FCFEF10D0CF3120FEAF705F9F6FA03F801FD0CF9EDF002FA0105FCFB00050A07FEF9EE05010B0301F90306F40A0200F40409FDFDF9F3F8F406F80C030D0DF803FE020204F8F7060602060501FCFA01F10F0EF9FDF8FB0EFC0C0D0A060BFFFE010A0AF80100FB0613090105020AF803FA0AFA0707FA05F8FCFFF50EFAFC0AF7FC1203F8F811FA0BFBF408FDF5FEF800FA0405FCF2040717FD06FBFB0DF501FEF2F1F606FC0EFDE80302EF07F802010DFC100005141005EAFEFAFF0C03FE01F80806F3FFFCFB0602FC09F6FC0AF4F902F2050F01FD1413010209050EFFF7F4F502FDF20C04F8F70705F7FFFB0B08F6F908F9FD03FFF4F604F80B0302FF02FA07F704120BFB040FFDFDFD020A080C0D01090AFA0C0A04EF0A000200F6FE04FF08FAF8F7050B0306FEFBFFFFFBFBF70CFCFDFFF71104FE0108FE0808EFF8000205F8000708050004FF0506E9F5F30B000906FDFE0F0E02FF06F103FD0205FB05F8F1F8F4FD0009FB0D0107FB0706FFFC01FDFBFE0006030807FCFEF80604080200F8050200FA0004FFFBFA06070509FDF7F5FDF6ED00F208FCEF020E060FFDFE01F9F4F0020B1309FFF6030B06F5FAFC0D0106FD00FCEBFC040CF503F904FA0B02FBF7000E07FB07F3F805F403ED0505FE0203F3020103F40A0CF90F030C02FDF80600FF0AF800FE0DFD00FD0207FF0501FBFFFCFD070701FE04FB000AFC07F4FB02F9F002020E01FEFFFAEE080E02F7F3050703EE04F804FAFCF8FCFFFAEE14FC03F6060609FB01F7FFFBF8FE0C00FCF70504F7FE0FF906F4FCF8FAFE05F905FCFAFAFC050809060CF802FBFE0CF2FB00F4FB01F3050C04080507FE01FFFBF70A020A030300FC0802F2080407EF060B0D0E05FAF309FE00FE0EF205FC200101FD01080BFCF80400F9FD09FDFE080101FC08F8FF070005F708ECFD021006FEF80EF701FDFD0F0804FCF706FB06FC030802EF0C00100607F40AFE090704EB03FF0806F90103F900FDFCF7FB030E0C080C01FFFDF9F9FEFD13FC0006FCF901F8F4F6FBF90205000CF20DFDF404FBF5F5F30605FFF80A0306FD010001F80706F505FD06FB07F3F7FB10FAFF0F01FB0500F0F101FBFCFEFC010403F8060306F8EF000600F3F7FBF70A08040D08FCF9F801F40DFD02F9F805FFF8FFFBF7F80FF709F6080E000EF50AFBFFFB08F002FC0FFD0000F7F0FF07FFF7F9F30FFF00FBFE020212F2FCFFF7F6F503FAF1FDEEF10000F6FEFD01F9010CF90102FEF6FF060303000005FC03010407020202FA0BF2FA0A13EE0D05FA0B00F40E06F308F80F0DFBFEF80101FBFCF605F5FDF2FA04F801F7FDFD08050F0DF7FE070307F1FC09FBFC010CFDFEFC12060B190201FCFA0704F9040AF9F9FA0DFF08F8FE0C05040401F611F5F4FF00FC02F00810FAF303F90105FDFA030E10FF0113F7FEFBFF0000FBFDEC00FCF70FF9F1030A08F90E06F9F00E04FF050A07F9FA00060900F40AFAFF0AFC0204060601101AFD06F90109FCEF0407FA1001F702F702060F0CE9FA05FDFCFF08FBFFF40100F408FE06FFFD09F8EC00FE0101FEF4F1F8FB030011F4F404090700FCFAFAFBF405FCFD0B03F2F7130AF7F501F8FF0608F800FBFE080DFE04FB00050B0D00FCFEFE0CFAFCE800090E06FBF9FDFAFC030D0BFD000E0808010AF1F9F9F8FEF90BF5FA0E08070702F707F8FF120905FD01F5F6080300FEF0F8FF00F807FC0A03FAFC0A09F70DFCFDF110070BFAF7010B0703F80A0603FAFB15010B04F3FAFCF20C001202F91208FBF4FF00FE06FE00FE05FFFDF903F50101FEFD040A0300F70003F8FE070A0108FBFCE3FF04FEFFFDFAF6FEFF03F801FCF9FDFCFCF3F611FFF8FCFA080EFF06000C0C08FAFA04F5FDFA060901F40F06FFFCFA0F0EF607FBF8FD1202E6F3FAF1FF06FBF7EF01050AFEFE040403FDF103FDFDF7FE0402FFFF0109F4F907F9FB000CFA00F900FAFE0504FD07F9F70D09F30D0EFD03F1F9F50911FDF3090401FCF7040500F6FB050AF00611F4010101090509090AFA060003021302FAF4F401F0030413FCF0F8F007FC09FFF5070A19FA00FB09FBF9F90C03F00EFCF6F1F40BFAEC05FD07FDF9F30105FBF60D080B0205F1F901FBFBFFFE0108FD070A0DFE1AFDFCF3F3EAF604FB0001080505F50504030008EBFCECFE0A0603F9FDF701F90BFB0F02FCFC04FCF8F9FEFFFBFD1B060904FAF5060CF7FB0D000CF805110106FBFFFC0204090A0D0201E70210FC0BF30704030200FBFE06F9FD00FBF9F7F9F7FAFF0102FD090B14010F01FB0106030206F704FE01040C080E04040600080E020B040C01060B020304F1F9FDFDF6FB09020DF6FE0EF4FA0EFE020701FCFE0CF8F8F3040DFD0806FD00FD0A0B0001FFF5F407FB05070007FE0A05F905010206F0F8FFFA01F3FD090002F30B0AFF0DF103F9F604F501040805FC01060E0603F4FFFE0D00FFFC02F703070209FCF3F705FF060F02FA000D0600FBF608FBF7FC0CF40104F4FC0A0FFCF4F8FE05F909FE05FEF201000CEDED03F2F8F8000A05F90100F7FB0307F9F904FB0502000CFDF4FB07F40101F7F3F70408040701FE0D04FD040705FE08FF01FC06FAF70EF00FFE02FDF8FB04FB011803090103FB01FC090AF000FF0CF3000500F80409FAFD0E050FF5F9FC03040301030205FA08F8FE020A0A030FFAFC0D06F10607F0FDFB04FEFEF805FE0008FF0BFE05FF060F0AF7060C0A1103F1FBFCFFFE070EFDFD05FF04070803FAF8FBFB04F1F5FA04F8F60404F8FE07F805FDF405F5FC07FBFC10FC060205FB0002F4FF0CFF0A14030AFB04120EFF0202F4F402F1FFFEFDF9FEFD09070DEA04090108F7040DFEF10100130003F3FDF7FE05F4F3FBFE0501F7FF0CF9090B08F8F4FC02FD06F90008F909F70007F2FC0AF9F9FAFFF6050607020205F4F2F2FDF601060104F4000AFE0DF50501F9FAF9F90601F9F60CF0F5FE0EFFE8FD0004010D0B04F402F1FCFD05F6FBFD01FCF601F5050200F0F60401F906ECFFFFFC10FF0DF2FDFB0D030E06FAF8FA0BFCFFF50006F21000F902FDF401FAFB06F5F70A02FCFFFD00F5FE03F708FF09FB01F900FCF5FC01F8FA030008F90EFD01F9010CF6F906F9EE0503FEFE120D040BFF00060500FB0E0A0900F9F8110B0703FD01FFECFDFE09F8FC050306F702F30403F703F90B0409FBF501040400FDFD03030AFC00FE06F7F5F2130E080FFBF3F5F6F7F2FE070408F002F8FDFEFC0005FE0AFD0C071001000A09F70D0A030E03F8F20801FCFF0500FD1400FDFDFBFFF2FAF409000C0CFAFAFAFE020106F60701F40A031106FAFDF60702FAEFFDEE0605FC02FB0608F508FE0D0401FEF1F30005000503F609F801FFF509F60B03F708F701F20DF70E0B0FFD12F8FB0112F2FD0A00FDF911020800FD040005F4F7FCFF05F9F707FFFA0FFD020B0506FBF60BF90CF90D09080FFE0403FBF7080104F202FF07050502F406050201FF040403FB0506FC02F3F001FBFAFF03F711F5040AFCF9F7FAEF01010D0301F5FC08F303F6F7FFF50508F90400FCFC02FDFC03060502ED0105FC000502FCFD0801F9000A05080502F5FC09FDFD07FE0202FD060406FE06F205F1FEFA060600FB0200F8FBFDFC0E18F806EC01FF0BFC0603F4040C0209F9FB02FE0AF501F302F503F907FA030006F902F0FAFEFE02F7040FF503FD01FE0308FC0AF309FFEF0D02FF0207FB09FFFE0604FAFE08FA0DFE0000F4F8100F0BFF05F9FDF704FEFD050711FB00FF0AF8F70202FFFDFAEEFEF601F80706080406F6F107FCF7F6FD0D00FDF30403F5030A04FAF7FB0408FEFE0BFBFF0BFDEE000A050505010614020904F300FC01F3FFF60FFAFC0AFD01FCFDFC000500F906FE07F70D00F303030003F90A03FA0C0308F7FC030C08000A0104FEFB0301FE02FDFFFE04F30B0AF0F103F8070DF20CF9FBFD010300FA0EFC0F0CFC0B13F8FEF701F8F2F4FE01FBFB060DEB10F3FCF5FC05060FF903FCEFFAF4FAF80307FE04F9FA0D0DFB03F9070502FF03F5FE14FFFF0104F4180CFF07000DFF04FEFA0FF7F6FE020EF7FA02F804FDF0F6F4FF13FFF807FB0AFE07060FF0F9FE0400F101FF0A09F3060407FF02FC00F9F803080701F309FEFAF8FC0604FB05EFF4FBFDF30203FC0E0908FC0900EA07F1F8FFFB0AFAFFFBFF05EE0001FE0BF607000806FCFD0703FC01F101F4090E11F91CFA15030911FFF816F9FF02EF0FF8FAF4001016040006FC0907FD021105F50303F9F80105FAFB010BFEFC09F402F6EE0D0A0BF803FBFCF709F50114FD05F5F6FA03FE071302FEF7F604ECF3FFFA05FEF80506FFFEFAFEF8FA0602EA010700FCFEFF0EFDFB0405FEFE04FC030DFC01FB0B05FA0703F5F2FDFDF7F90AFF02F707FF02F70805F70200FFFB0901060B0803FC07F906FBFBF00402F900FBF6020F07F605FB020F0A0006FEFC071103F8FAF0F2F9F8FC0004F4F8060DFE0703FA0B051207FA080CFE07F600020102FDF9FBFAF80109FF0C0FF10AFFFEFBF806FAF607FDFEF6F702F6060A05F7F8FDFA02FE02FA060006FFFF06F9FF03F200FCEF090AFF0609FDF101100D09F7FE09FAEB04F5FFF9F21006FDF503FE01F500F510F2F811FBFC00FFEE03FB00FBFB13FF0603020EFFF9FF0A00F5020211050AF7F806FE0704FBFE0BFEFEFEFDF40B08FB07F30001FD06010AFAFCF3050E05FCF706070600FBFA0DFB04F90DFBFE0BF90AF9FBF603FA0EE8FA0B081006FC050604FBFAFB06FBFE0E03FCFD06070203F805040AFDF3F3F7010B030D0306F5060106FC0AFE15F2FF05F207FFF9FDFE0509050AF81211FA0EFFFB0AFFFF02F501FEF901060409F509FFFBF7F7F60309FE0403FB0301FEF711F6FDFF0F0C0DF1F6F6F7FFE50008F608F0FB090702FDFEF9FCFCE900F8FB0DFE0A080C01FA02FEF5FDF805FFFF0AF605FB06F606FCFC0AF7FA08F5040009FCF806FD07FCF701F3FF020200F8FDFE04F4080CF80BFFF60B05FCFC0506020600F505010E07F9F9FEF5EFFEFB0CF804F5F10A12EB0300F2F7F8F6F7FD02FD07FB050304F4F9F9ECF8F9FA02F9F1FD05090BF90EF70213F802F20111FC07F60AF30AF7FC0E08FEFAFA080206F4E5070907F6F50E050AFAFE0108F506F8FD0A0106FC0302070201FCFF040B13EDFCFE0103080CFFF8020CF8F3F906FF0303FBFD07FBF8000F0801FB08F5F900F90D0600FAFE020C0002F5020E0000EEFB06000A02050DF601000B0E001001F004FEFD01FDF6EEF616FBFE0E0306FC0A05FC0A01FE1106FD02F701FCFC02F4FA060401F00A03FAF80501FF03090003FDF9FFFE15FAF500FCFEF80304FE0C01EC08F81400030006000CF204080606F4F6F7F507070405F80702FD10FFFC08F5FD04F5FAFBFD0403FD05FB02F8F705F0F806F8F8F6FD0A0F01FBF6F208F9F9F80408FEF40202FFFCF106FBF8BC708C44E3D8A298F708196BC66F75F91732E5AF062775A9ACA36CE2DA64BCF62CAA4F63293C7A8F894856E9F263EB9CA4A0648141B4B0EA3A2D3364C36A830D2925B3AEDDBC06C0E20E2A62402197013912B590B7E62F79551BB5F8631EC49F9908DCC11CEE2C");
+//        int smlen = 5697;
+        byte[] sm = Hex.decode(
+            "A39914B0CDFA5DB70F664967C7FFD85E30F130BCC1457C262D005073D185069A1D5D6DA2F31BBBCDE99B7B7F8EFADD046F060226D06A549C6A3E7A9E7995461CF2FD5482CCF48AB175376F780EB6C14FEF90F90226225D73BD5731F62492D53E80B6C8E64AE9973049B2B644F896BF9B9913AAFA1239EE214FE8C04722918DDBF0D36FA5326BDAEDB17D21A19E4DB1BB8871FDFFF0F3406D1813230118019E2A3613F7F3758A4851024BA12F335E694EA817CD3420320A83C14297873A1015D0D9E9CD0AE3CAA15E4E10226C5D75E41A716AE070F41AB800D460A7589FC42CB189752396552820D7223E08FE494D1A67231715EA7B571471EC6348AD8F6FC29CC723FAF32F0447A4137DF80908816F0401DF7CB2B9A418A759CC15D16C2AB2CFF54AEC5745CF66A3C688CA2939172AB518D35A78EA29D1C6BB3FE232FE3EFCDFD6510298F7ADF7CBC2E9DC20E8E2088EBEBAD61A1345DA2CCA74749EA8022C34B85B553CF71AE811B02C3B246C58A49DB06ECA90F0E2D2E985A90B87B0E6C91034C7BA638B49AF6F36B8A52452B62176C2859A665BEFC409734B94B4171974C8DA284633B7FE408EDAC63456354173BD551F667F143A8DF2B714618873F8E89C574C44C90F2A4A0243E5ADD225C88B11CEB45922444BA3A05037CBBED4903936D80B78EDC13DC36D0EC4737E5CBA4B06F78E0A0A95DD028F22594409819B1CA2E97DD898F1907613CC8A33B8201AC0102F0BA3FAE7A899168A840CB831AC81C38B0449328C2927BB7574C1507079EBF31E0C8EF29921082C20365FC500CBE05468CF100524DF7B555FA7DE4516F959181B74C17198450B21413BB5C8976EDC0DE4C30BFED2FC86A9E64088AC8EE21C754D6422BF52BC25DD639E9472811D0871299D7EAE96DF0E4F456FE902174DBE226EBA758BE45DDA51F34B080A75E5CD41B80053763A45776439D6E1962083DFBC9456DF85F8B59497BD6BEFEA82AE07EA226F8F98B68B427B5E31DBC73F664608F30F30FE66C2251DA74402596A4E763548A78481D3B1D5A980D480C96ADAA894ECB4E748BA44C77D283B713CAD0429A03192DE20E48D4B018BEC75DC42165D6731061DC5698B0A4DD498164367F6D7908F416AB1A23850C1B6A8C920D05FB320A4DECBA746C48771B9F88AF89AF48C357F0D9B15FD66A37A910A2AD1EAB0FBB05755543429E1FDB24EF1EC95575787E98BE920801887BD305F64FB3E296AA874B26F8CFECB3799B20353111D479E5448B411F3D7D06AB37E4DA1D9F3AFFEB108D217E721DF56DDFEEA7C4F689C40F89FCBECA9E9EA0E669748177B4B8A7817A5323B885604A299F9064E1DD1A3839C92895E6831FDFC452C4EEA673E7792E09FF16A3F8BDBA226D1B11067F7ED559BDB11C0DB787C3B76880D8F6D4361E97DA2F49272E7C6EBEF9245A7AFE436884EEB790A023E77222FB6ECC573FF62547339C162EAE18771BD292091AF6AF5675F308F7E6B55F3D2B5691354EBA38D93980526247F528459BBD190C536C736289751594C760DAF4B48563F1DD427EF66BCA3D441E6FA040D24C989728F60DAD6821C53C9A5C0DFB21ECC2C5C9274CA432AEFF0457B64F780C9CF4544E6145AAEF1B545FEC59CCAB2003902AFD0D3EF74D54D48BAEE004449EC8B7378779AD33507B17B9A469F0CDD10BFC2F7B35AA47A756492B6A56D43089BB256FF626A4CBDF9E95C2353B88235C7D887AA6C75696C51D06A62619FB4D05AAFDD73A850CDB8CB4348F55E4D08CC0525F9C2090D6CEF134FDD9F3EA466F20E2F7FE380CBF90BF954B187F6F6B33DAAD6BF5DDB6CFE1AEF8883494F82FB8C9D5F3B6474BAEDC3F14F9639C97A132959E67C7BAF81D2D4FF943A328F6CB334D59E3A90B5AA3883B93903D2650D2407CAA2D59953F05B977E3684DBC44498B9E2A9CD33120E587D5D802EC10DFFCDC4303FA8BBBF8CE347AFD69BBD71C339BBA1C21AF6D04B71055644573AAFE2E336884E2CAFF3DAED60F5C2338DE4F360B9FEA858E662F69B7F8A317279F2897440F7A1350DBF9B96F876D941F5E65B5D11EB9C31B637F39F94D7C2BAFD64A1C555D3D7BB9DD5DA3AA187B942C61CC51C782DB764926069D38CD9B3762AEF4A5008FCABCCAEF6CB405F931C2C3E4F849BE9212F05551590BE18E3D395FF5B484986F3A1F26C7EFF1DC10A82BD6E795AA1200929A899F6DE3538C6C665850B386C2973905C72D6A2D53DCCFF8BDD2DE1B20E43B4A37C0BE79E6EEA234AD401A97BADD13F5913AEE73BC8F0D04FE0418113E7677691C44F1E02D91A7F02573B43B4A00E0E00ECE6F8C68E8F55EB6B67DB373AAFBBC263AF5542FF32E84144A89E190FF81C0B7E003FFBD1D07AC6FE3FCB6BC47E9F0B11DB941E0915D1638F03C7BC5BD2F5A24F6602C52A58C977EEB18440F7EE7777310EB5B5B7C9A4E8BBF67C8A56A5A73B273BCF23B000B2CD4F962BBD5F47BE5549506E1AB386967ECBCF22D442504B3DB82ECA0C80559D5EF36D28A365AFE8B75D4299F57DCFFEFAA76A8D24B19D57BC9348745B040378733587C45F733C8A0F5E12B77C6358018429A7A6F2BFB3A0B4D466D1B77FFE304412909F2910E5883E6014B95C9DDB5F15FE6A4557D1AED5D813FB7E1A5C6DEC77766DDC043D823165BC6F5A149BC861C1109384176644DBD0DF07D4661F641F38FCA7EE06B1C6136539F44912443B489FD43A0CBA578BF720BB686F35E5927AC7CE727CB610922E68653C0FD79BF2EBA045CC2D9751D679D70532369A727058256627F2C0BCF8329C0EE7C9CF7D4BB4D9FE9480896B8244CA6E68A12F742C35D07095B610F62A052F5E3548BD91049FFEA6A1BE6C098E41489CEB3E449F53F4F26F75505C245DCAE948B1A03181B965B824302BD7231C69199BC24EE17C1D0889DDA5009DB0A04781E697927E4B95E0FE0341EB494EAADE361918F946A27C7BF87DB98F7B80B0A7E27C5E79FDA4C03944119E8A5F6FEAD8FF952741F6CD397790433CD441FD50D22608EF38544E3B069D399A1E9DE1CA5BF24A4E130D27180D272EF0A004DBDA49BFA8D819F5C12912B10F5E4939F6BE108E85B7DFF508C49DDEF6860455EE39A2FF4F774515C7ED0E21835BF3F0CFD22B9D7C0C4EA1879BDE0C615E10C058AD36173DDE3F48993C03F1C8CF7C055B192A3FDF754B4CC6F12923D83AC127B79170FD8336BD1A955CFC35176791B63DB8A86C4B8659FC0D2EA98C8D5017F01F0BF40763CF6539A474A968F4AEFBA932F83915ACC51DA5DAA774662087EAEA8FFD2B6910B6A918ECD54DD715CDCB43302140344FD17BF3211F780FBB46E603440C9B672194EAA255ECFCD151FFFBB9BABB80016C3E7769E54975B919D5E3B8296383E46681033A7828323CD2CA8B5C3AD5FB2450C994B0637A8F686C4343E2D8BBC9E6D9569EF77ECEF07C012C4F950DCE9A6F8E9241F79E11DC951CAC42A8B43EFA2CC7A66605334DDD7DB27146B69A8B2A0A0A3E26C68848F63F0C6A381F7DE899FDCD311A64CC0C1C211347C98C001DD1E4507F2429E9547D4DF3F7A21A6E124A12D79EAE294FA0CF4C87AFCB0BADB1484E34F8362CA2E3484AAB32E309A8CDEACEFBD9FEAC9EDF3953A069FAAD475165A034705B600173D6A8BADA4BE2B0E62711F51F5FC51029C4A9DDEC9D2573049EA11689C2EAE4D7C1587DA094A38833654CB065EC50B5E6B017201C2FC3F7551FA92B78A378B5C73A76ED06972A9A7B2D0FB6137BEFA4B459B0F22CA5DD6F4DCE8B59E32DE0F28C0E10DC45BDD52ACBC156A8B17C47B670E2C050C4CC78FFBAA02989FB903591981533B90A59DEE93280D6CD028A1F152FBA5130BF3DC1784CF809CA550F4F0EC832601514D5E85E4D852D37A513B5D7A31390AF745918114D571E50C85F0C6FAF58F58FC38228F10E48EA875280F5878DD146B8EEB4B899D0A5F27A158C904876E061CFA39FB93487599D6B24EA186174016E5CEEE068A898E22746AD896F5F0D2EBEB208EEA47D7ED6AC621B61ECD1C77265DEB696660F1EDE7B80C3407659F0E0655406FA7DBF683C3008C9CB8078DEAB5B287DDB7B411BC2E8069C9F33947778EB3C38F1CED5DDBB24A23C67924EA8FE8FD44807AEB7B68B9CAD2FEBDB1F94889F897AA0EA958B0EDAACA37FA3C2F1AFF87BBA116C8DE1C9867D5176E92F7486DE604846807E21CAABDABA1D5E359A658DFF1924DE527BCF33C72130417A6F57D552731CC092EFBED3FEBC335897EE9CB1C7C1551D89841443F71F85995BC4241B81A74DAA23891615FBCFB894929A91F2B7C52A6F6807924C06D4DB892D1F7C9F771A2A3766FA22C13F1DDA0AC89270F87EC21E88881D3D203983A6E1841CA1041A1FDF447C74D85C5EA4E71C068DED9057730B8800DCB5DCCBB7D47FB468F850F15B052D1E1B4FFD5674D902907D26EC68101D9BDA68289E401E42F6A09F2D139C56CCB4731827E21F3964E782924C7B5E76A86F9EF91AE3EC0D851F3801CC71123B1B4842AFAB8C664BDD701D9399F5560902E461D871ABBD271BD01D05058CF7AF39FDD44B076AF908DEF88DF0D8BF6DA415DF5638AA7A61FFA4F5CAAE96ABFC02313829AB9FD5133C85413305AF2D0441DC0F1ACE093C2687D062BF22CF713A2602F4F1245CC6CFDAA67FDF4047DB032E4028650D3F838E427EBF5DA3E5F02D2EC2F271D3F6224EC112AE3B734E54BA4DAEEBA864F4EB68E4CC2F4817515E9825CA20EE1FB5FE9FF468D945C36D7CAE092CFE12F004574EB85CFD8E2F40B068B284460FB19FF0EE89602EF266815CE5B6446BD0B36FD625FF8AECC166D0E7CB67357DAF11E1076FB8102B6BA3FA3F38B999FB80915A34E3611480399117E9B45E3F4C6C9D38CE04C0073064D713AA317324E5FB3AC82F945452B80659DC984129292A18952CE0358EA19AB9924FC8C19EDA391D8A6AD85FE86D5F7C050CCBA89A016D586F0EEAC0CC8FFE732E5DBBFF7292EECC2F6854138F1B7AA5E6774DFF874E32708021A643F31BBF9D07806E1BD5910E6D7AEE92889681FC7A8C9A124A217D1C2B561390B20E6DA80113BC16EE2672CA989AD26014E2ECEF32C797B5FDAC2BBA9D17D76E467D89BB6413C618BE156B622D4A72C2E7C0DDEC24842408FB5A807805D067442190E149C471D739727D9B52F2F429924A8406C1FD5E1BE0E31DC3307AEB218E04CEC8DEA41F1522DF93E5FB1C5488A1B00461BDA1EB7C35BDAF90C5E9B8E2826398339974A0E2F4A6BD7D50F4CE62680F78D5DAE190E4F6989D2556CD4E38F3E6CDACD2397F787DA0776526DA52312BA01721F3232BC88653F036249794AE10E883550532A8C434F85BD5DD2A1A4BF0C69B2018B28C489F392106868FBD0A02D6C2DC9043A3A4AB9C0EB2F1CDE8F05B169FD4070C8DEBB31CCCEE94A6F0A94AE65DA0B0990F2EBB37B0B475B43B6A15984CC397D35AF703DC1C850AC572C204E7A5372B0ABFBEE7D4C7002F66373A2ED5F46F3AFB53ACC555FBAF6C579CC54C5209B8DDD27F4D9CC37530431C4A2AE972D1938F3FE25B5A34398FC6B4E8FBA9BF0CD1F4EC95D5B8FE3B3594A78AD559BB1ACBA2E3EF6C57C7582DC5C3B66A6A5971062BC52E5A07C98090ED33616A487FBC48D6DA1847864EF5E4C1601AE2E39AC00859F1C3A44E64C91BA974002EA99D8B64EDDC28DC83B9F84E7A7F9F0E76A4BF48B49B4542B4E25F97DC9E7866CC6F9521EBE481C07970C1DCE74DDA3CF89A4C2C912F45FFADBF6D818910C9D8792E83A54BC5D43E4E51F6785CC655375FD048D0CA578B2260F6A54A06BD59F4FA2127B19DCE0524879C7E33AFDD31179C670D6B3B761A94203DDE6312188C4A4221A95CD0C58B8EC18F95B820CFB25343732687B2D1E25C5870B33CA103A6E44021346A0A9C3E47D516DD1A1A026659681201E9EC7186EE07F083B93051E3031DEBF11049A5F00647ABE343D8A8F3D317D473563C7B242B6F62A4096AED8FCD846314A8873DCF93F622B8331E7AC7F9AB9C89609EBB7AAE8E14A8DADBAB7693B5F3B1C05A0EFC8BF9D09C12F86FCF7BA1CC97BC733E894E6D2C0CBF8EC701F0CE327E91DBC9DC935712BA8A0C3965E26F1D479C1F3CA4FB5DC185CA3A2EE93D411BD536479B7BDF28AD13921EC74A25B48C7E7F162FCAE7607C9AEA792E376A685ABCEC34EE956EFC28E8EEDA2F5564118F0E02D54B0D4F74A04E8E109C8C3569FA51F0E255169AABC315065F27720120FBDA091CF7671E3EA37F15E496040D526332CFA2B3CD86A108C9051C9930CF5D4F2D3A9CF6BD697995691BD4DC9CF75FCB1AB68A2744D2386D636C20746EB34F621520615C4FC0298CDA3779249B9E5BCC1E766420FED68EC13D34AB896EBE9ADF82575BB369A5BD9927A4D5237E8823D94793BF6AA5F4C5637136E58E31983EC575EDF37D312653D856E54E0B1A3A41FFBC14D5A9788BAAD44854A1888625EE0BAA13673FD496AEC9FE9FFC8FCBC1A7F3982F99B8342636E1745FB3A79E426E4DBB571F0CAD814687F48E5D68E4ACB1A53F3BC4FACAEDDA74546D7100F7FB0A26F81CFDF964539453750902F01DF5064B06ADEE1E8075C33F2B067EFB264C6F5C5A04C6C0E1003445A2C991653C233F31DB9CB310450271228AF195C30616785588A38D784B66A8E5AF902ECB54416720B68719581C0B1FCC5DD585F02FC9DC76B252885B24C3757717B13AA12BA7A34686C030F8BBBD8FEDEFD502AAACE53B06AF851330B5F18C3DFA16C49CC9B8986E5EE03955021570E6513CFCA5E875DD587E1C7047F17BFB970A09FA31957A41C5BC598DBD763C7967DEA706B741C6551983DDB78CE7C9C7A41E83C5D05E41F92C03DC518BFABEDBDF6004AF71DF9FFA509B867FF8B04FFF067FE5079CE5CBA1F81DB2F0A02CFE5AD440884FDBE348ED1D081A8E49FD26D45FDD58A4D00A6F623BC6500772B1EC7B847E07DEFF96AA54102D24CFA58E24BE90AEFB11EC5DBA9C1538D1D486F566F9D26FE47484850D158F1EB2903F7991ECD95FEFEF3A1B122FD81CF7D0A63A3B305F6AA66E772941E7317FA233050D64E36B75C06F4F031EA96CDC0C0A0D793BCA96421684E527D55E85C28CF8297AAAFE1992A449E9CDC64742BE4725102D79787AD0D03DD5F989791495D28509CE409B931546FB7ABD598D982DD0C0B32247F1E4ED312034CE9FB285C325CBBAFA0F9732D7892BD6C3A3137D3B6AAF4AE9B2A8CD681A0AABD174CF38B15F34F6D1435B3CC9B48483DEA532D0608D6C937F5DAF66E7277B8CAE3FAF6915D005E0205CA099F64E82BEE0EF51A39C14C07BB5066E02D1C5B9D00B41C464B24189635792A0E5D92B6E5E33BC6B73461EE77E087A0B16DE616A77BD1D5E480E1F3AC3C9FBF1C83E5F463390F5B3BF27774F0150D8CEEDFC20B9BA1BACB7876A36B76FE3A0723CA3BCEF381C895DA17FCE60D3D3A548CBB5679814030309B49A041DE8DC02858597D59E94AB4B768B4681CB86BE53ED5903D033C0ABD3D68AB066AD41DE359C0F9818D08683FED12B035FEF4CA7CE45C1943325B283E2B836902E237AA232CFF79E88DF21474C23907030A6D34EA586FDFEE1994743FE18ED6BB6C3897B943A06D301C86A0E3D374F6389026E02C38E5372045089779DB7ACA13DC0FFA0CEEA985AEB4E1C736B7B620F7DA790E8FC00DC6B7087BFB28E2C9651DBDFCAC37EBD5A238D653701B8DA6561A5E97137349C3E1D9F4AEC3EF1824EF75EEF32CF453FDFE06F0031292F911F77ECA3C9FE046E6A2BF20971C93227785D1B9282AF70CA5125266311E246BE6F2C3B2812E05167B13E1B23EEDF9A46A91BA583BF37A38FE01DBB0C844DE3B3BEBE59BD9B731143FA56539BF3EFBADD5869AA5159109016C2ACF46740B0447808EE55926FD1E5556DE103336040FA9BF0968CE67B92376F6EA838E957E0428E85D3ED37ABC14208FF22CD4C20D9F6064C35FC36AF9DF6BF83FCCDCAB37062029E05A80AB1B86AA6FBBF6688D4BA73C722FED6D9D5024DD81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");
 
         doTestKAT(PQCObjectIdentifiers.qTESLA_p_III, pk, sk, seed, msg, sm);
     }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSMTTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSMTTest.java
index eec4c90..33569d5 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSMTTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSMTTest.java
@@ -4,6 +4,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -15,6 +16,8 @@
 import java.security.SignatureException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Set;
 
 import junit.framework.TestCase;
 import org.bouncycastle.asn1.ASN1Encodable;
@@ -33,7 +36,6 @@
 import org.bouncycastle.pqc.jcajce.interfaces.XMSSMTPrivateKey;
 import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
 import org.bouncycastle.pqc.jcajce.spec.XMSSMTParameterSpec;
-import org.bouncycastle.pqc.jcajce.spec.XMSSParameterSpec;
 import org.bouncycastle.util.Arrays;
 import org.bouncycastle.util.Strings;
 import org.bouncycastle.util.encoders.Base64;
@@ -78,6 +80,9 @@
         "MHIwJAYKKwYBBAGBsBoCAzAWAgEAAgEEAgECMAsGCWCGSAFlAwQCCwNKADBHAgEABCDIZh5Q96JIc0h+AmYHd3UP1ldE5buCIeHXsN" +
             "xBgGEtbAQgxENVtn9cR2bPbe3IZcmy6JmI6fvHt5yMkJ1lgQZFw6A=");
 
+    private static byte[] priv160Pkcs8 = Base64.decode("MIIOKQIBADAkBgorBgEEAYGwGgIDMBYCAQACAQoCAQUwCwYJYIZIAWUDBAIDBIIN/DCCDfgCAQAwggELAgEBBEAudnn+Ke23VtfdCDOOmoiM7GeSVIOajbo5dlLU+HxL8kMcaMuu5rsn7xDWulFzszhQcgLRfiMJDeXrfVLbW7mWBECNIikL7LfHjTKZ5ZVngacE1FFPdJZzVYc+b7oSHlpkiCeTtVw+0Y/flVyOXVvMPfJFLy/Tp16GDv7Lq9PLBVz9BEDFtpfLrsihaIGlvzT27V61ulilXUJciwAExs+5VWI0Z8nGzuzEKZr5twDz0Zi5y4IEMl+iLJeGyCaLia8l+S7cBEAn2An1hQJ0oPgbl7n9HDL7Szxfdz2Jnck5Bm1I4jrMpRic9E8+kb5yR3SutpV5q7He2Qo+A+9H0d61rw91LOFqoIIM4gSCDN6s7QAFc3IALG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEU1N0YXRlTWFwz+vLa6D+CbwCAAFMAAhiZHNTdGF0ZXQAD0xqYXZhL3V0aWwvTWFwO3hwc3IAEWphdmEudXRpbC5UcmVlTWFwDMH2Pi0lauYDAAFMAApjb21wYXJhdG9ydAAWTGphdmEvdXRpbC9Db21wYXJhdG9yO3hwcHcEAAAABXNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAc3IAJG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEUwAAAAAAAAABAgAKSQAFaW5kZXhJAAFrSQAKdHJlZUhlaWdodFoABHVzZWRMABJhdXRoZW50aWNhdGlvblBhdGh0ABBMamF2YS91dGlsL0xpc3Q7TAAEa2VlcHEAfgABTAAGcmV0YWlucQB+AAFMAARyb290dAArTG9yZy9ib3VuY3ljYXN0bGUvcHFjL2NyeXB0by94bXNzL1hNU1NOb2RlO0wABXN0YWNrdAARTGphdmEvdXRpbC9TdGFjaztMABF0cmVlSGFzaEluc3RhbmNlc3EAfgAKeHAAAAABAAAAAgAAAAIAc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAJ3BAAAAAJzcgApb3JnLmJvdW5jeWNhc3RsZS5wcWMuY3J5cHRvLnhtc3MuWE1TU05vZGUAAAAAAAAAAQIAAkkABmhlaWdodFsABXZhbHVldAACW0J4cAAAAAB1cgACW0Ks8xf4BghU4AIAAHhwAAAAQOKFxlEfDKYm8S+/EMVDkLhX47KYUOA5vEYEZqxG/c+Z6Oirs4GEuCKzsftFyaMRIU3y5DB9Bhl7imrUoYwUEj9zcQB+ABAAAAABdXEAfgATAAAAQCpFo39SD5glu5m33Nzo6GJiU5+uhLzZcliwbs0j//DQ1/VWRWf1lWzdi1xtS8rd/R2dTnuooKV+BEGDBy8AoBl4c3EAfgADcHcEAAAAAXEAfgAIc3EAfgAQAAAAAHVxAH4AEwAAAEDX0DMQrzl+doThBFh5SecvYw39xuVvKCePhCkxsmeT+i2hPepklJHgMvkWsl0I/3TH6FUNz1KSaNYTlRt6Cg2TeHNxAH4AA3B3BAAAAAFxAH4ACHNyABRqYXZhLnV0aWwuTGlua2VkTGlzdAwpU11KYIgiAwAAeHB3BAAAAAFzcQB+ABAAAAAAdXEAfgATAAAAQPmuATA13qaf2Ku2atnOrK9Ofr3wpX6/VH0Dyb1W9iVMccKjMAyumMdJT5gu67yEcmP0IRqQ6Xd8dEf4+i7/tSV4eHNxAH4AEAAAAAJ1cQB+ABMAAABAKtAKcSwI9cc4kerPdSa+i2m9W30u/AZbMno+OWfUX3cz3rIpbl1IkvV9nADkKF/5dujUnbJt9u3AWuWQbTeaCnNyAA9qYXZhLnV0aWwuU3RhY2sQ/irCuwmGHQIAAHhyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJABFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAAAAAAAB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAKcHBwcHBwcHBwcHhzcQB+AA4AAAAAdwQAAAAAeHNxAH4ABgAAAAFzcQB+AAkAAAAAAAAAAgAAAAIAc3EAfgAOAAAAAncEAAAAAnNxAH4AEAAAAAB1cQB+ABMAAABAdhJ+VS+t9lVe43B1NiyVAYZrJDzEQDwle1XLxN3HLxpW9GZiC2BzwjsQMWdS2lUYUA+bfZ3W4pEgA5LDS0oC23NxAH4AEAAAAAF1cQB+ABMAAABAj7puZ3XpdnFdAztGhXRh0+rdnvoCx4rj7m5HmKbbEh2uWNigAi8Fh2pRRf4qOkPvG0OhfBX2dbpXoR96L4tSk3hzcQB+AANwdwQAAAAAeHNxAH4AA3B3BAAAAAFxAH4ACHNxAH4AG3cEAAAAAXNxAH4AEAAAAAB1cQB+ABMAAABAaAWT0i/fjWqyZo1rOeA++CSBuaGBwHklZ11qzV6PbCIwSD/Vg0vImyFInqPjqxmdwXVQ46rfEOIgYoyuIBw+J3h4c3EAfgAQAAAAAnVxAH4AEwAAAEDu57Ns37TocQe1vyEP8jFVLFpPdmKwHo/vVc9Uy/kbm+i1dNwmdImRoep5Y7C1uE2TOzzIq/6S4fvXXZGUaZkgc3EAfgAhAAAAAAAAAAB1cQB+ACUAAAAKcHBwcHBwcHBwcHhzcQB+AA4AAAAAdwQAAAAAeHNxAH4ABgAAAAJzcQB+AAkAAAAAAAAAAgAAAAIAc3EAfgAOAAAAAncEAAAAAnNxAH4AEAAAAAB1cQB+ABMAAABAX59SOH68Kwoni/Eo8Kc5aFPjPyl/xSxWWcsEHYjpPg3vPLlVW6GrS3qEPrb0b9XHAU8md6fl32UK+4eGtWytl3NxAH4AEAAAAAF1cQB+ABMAAABAKqJnYJ5pIk0foEOEif93OS8jg27FYwBNd8AWXv3gtPdZmLJqPE2H+fRsoBVMhZUywW007LPRKwO7T3vZLBQTVnhzcQB+AANwdwQAAAAAeHNxAH4AA3B3BAAAAAFxAH4ACHNxAH4AG3cEAAAAAXNxAH4AEAAAAAB1cQB+ABMAAABALinGtIffKnpSofi5sxRf7QZ9UBZu+ix8K+f5j5ojUKCZEZeromAbfPDu9bPcMjAaVzzgPNZMrIMYpoDApShHiHh4c3EAfgAQAAAAAnVxAH4AEwAAAEDSTEsEnbFcf5ao6xfyrP9qVyJnGsAg3Jj/+0jfmZuRSHozbO+hMx1DfkOX7ULARItyLz/uBEJTKhzJ8wbPYxy5c3EAfgAhAAAAAAAAAAB1cQB+ACUAAAAKcHBwcHBwcHBwcHhzcQB+AA4AAAAAdwQAAAAAeHNxAH4ABgAAAANzcQB+AAkAAAAAAAAAAgAAAAIAc3EAfgAOAAAAAncEAAAAAnNxAH4AEAAAAAB1cQB+ABMAAABAn/ocApSgmhfhFGz9DUK5Ca9T/xBI1xYLnhKk+06qqgnI/m/hZPXFTT2iKkCkMFxxZZZvH1pHtPOVKucVzdTl9nNxAH4AEAAAAAF1cQB+ABMAAABAwMo1+AlTk7wlvgPdIf4sOm/f5VFJ4ZubfqUDh43EAmeAOdaUbM++iAKnOrWHFU5aiuZU0nnR15e40EEdmwE/gXhzcQB+AANwdwQAAAAAeHNxAH4AA3B3BAAAAAFxAH4ACHNxAH4AG3cEAAAAAXNxAH4AEAAAAAB1cQB+ABMAAABAa2wwqggyI/7fOJJz96Ud9GyJwVFCDPNvyp1RvNHmeKBSdK6/nC79RGdrB2wjHQSPx7RDvlfhH9XwraV1MIe+/nh4c3EAfgAQAAAAAnVxAH4AEwAAAEAGqXUgEbqwAbVZ0OQAJ7bMTqAq2Sd1d0/SyMLKvtougsFrW3wp+TjQW5dHaC+REmXKVGB1/Kcud7/KKOdElMbPc3EAfgAhAAAAAAAAAAB1cQB+ACUAAAAKcHBwcHBwcHBwcHhzcQB+AA4AAAAAdwQAAAAAeHNxAH4ABgAAAARzcQB+AAkAAAAAAAAAAgAAAAIAc3EAfgAOAAAAAncEAAAAAnNxAH4AEAAAAAB1cQB+ABMAAABAsm/m5e8EEdC7i6RlxZv3doKi53tJ2wYL7HgJS3iLQ3lX5vT5yFeNf2/AtFxPjknsCUrUwZEl4gFC9ART5uqhCXNxAH4AEAAAAAF1cQB+ABMAAABAaSEm9dO7+1WwgJQp8UgtVyKkB/PsEnOYvBLxeln5/xRT/dvzqegG0vEVgQJ5Oc7ZuJN6zFYqU8/FL/Cw8O1Q53hzcQB+AANwdwQAAAAAeHNxAH4AA3B3BAAAAAFxAH4ACHNxAH4AG3cEAAAAAXNxAH4AEAAAAAB1cQB+ABMAAABANy0J1WG4LPGiHCBy8aQVCB0EH+rdKYCkgxBfNO/9q6ck7M9m23T+dFY7+ZugiNY/eY5G41yYN17fnsY5YNxmmHh4c3EAfgAQAAAAAnVxAH4AEwAAAEAn2An1hQJ0oPgbl7n9HDL7Szxfdz2Jnck5Bm1I4jrMpRic9E8+kb5yR3SutpV5q7He2Qo+A+9H0d61rw91LOFqc3EAfgAhAAAAAAAAAAB1cQB+ACUAAAAKcHBwcHBwcHBwcHhzcQB+AA4AAAAAdwQAAAAAeHg=");
+    private static byte[] priv160Ser = Base64.decode("rO0ABXNyADxvcmcuYm91bmN5Y2FzdGxlLnBxYy5qY2FqY2UucHJvdmlkZXIueG1zcy5CQ1hNU1NNVFByaXZhdGVLZXlqnHIO+nhRswMAAHhwdXIAAltCrPMX+AYIVOACAAB4cAAADi0wgg4pAgEAMCQGCisGAQQBgbAaAgMwFgIBAAIBCgIBBTALBglghkgBZQMEAgMEgg38MIIN+AIBADCCAQsCAQEEQC52ef4p7bdW190IM46aiIzsZ5JUg5qNujl2UtT4fEvyQxxoy67muyfvENa6UXOzOFByAtF+IwkN5et9UttbuZYEQI0iKQvst8eNMpnllWeBpwTUUU90lnNVhz5vuhIeWmSIJ5O1XD7Rj9+VXI5dW8w98kUvL9OnXoYO/sur08sFXP0EQMW2l8uuyKFogaW/NPbtXrW6WKVdQlyLAATGz7lVYjRnycbO7MQpmvm3APPRmLnLggQyX6Isl4bIJouJryX5LtwEQCfYCfWFAnSg+BuXuf0cMvtLPF93PYmdyTkGbUjiOsylGJz0Tz6RvnJHdK62lXmrsd7ZCj4D70fR3rWvD3Us4WqgggziBIIM3qztAAVzcgAsb3JnLmJvdW5jeWNhc3RsZS5wcWMuY3J5cHRvLnhtc3MuQkRTU3RhdGVNYXDP68troP4JvAIAAUwACGJkc1N0YXRldAAPTGphdmEvdXRpbC9NYXA7eHBzcgARamF2YS51dGlsLlRyZWVNYXAMwfY+LSVq5gMAAUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHBwdwQAAAAFc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAABzcgAkb3JnLmJvdW5jeWNhc3RsZS5wcWMuY3J5cHRvLnhtc3MuQkRTAAAAAAAAAAECAApJAAVpbmRleEkAAWtJAAp0cmVlSGVpZ2h0WgAEdXNlZEwAEmF1dGhlbnRpY2F0aW9uUGF0aHQAEExqYXZhL3V0aWwvTGlzdDtMAARrZWVwcQB+AAFMAAZyZXRhaW5xAH4AAUwABHJvb3R0ACtMb3JnL2JvdW5jeWNhc3RsZS9wcWMvY3J5cHRvL3htc3MvWE1TU05vZGU7TAAFc3RhY2t0ABFMamF2YS91dGlsL1N0YWNrO0wAEXRyZWVIYXNoSW5zdGFuY2VzcQB+AAp4cAAAAAEAAAACAAAAAgBzcgATamF2YS51dGlsLkFycmF5TGlzdHiB0h2Zx2GdAwABSQAEc2l6ZXhwAAAAAncEAAAAAnNyAClvcmcuYm91bmN5Y2FzdGxlLnBxYy5jcnlwdG8ueG1zcy5YTVNTTm9kZQAAAAAAAAABAgACSQAGaGVpZ2h0WwAFdmFsdWV0AAJbQnhwAAAAAHVyAAJbQqzzF/gGCFTgAgAAeHAAAABA4oXGUR8MpibxL78QxUOQuFfjsphQ4Dm8RgRmrEb9z5no6KuzgYS4IrOx+0XJoxEhTfLkMH0GGXuKatShjBQSP3NxAH4AEAAAAAF1cQB+ABMAAABAKkWjf1IPmCW7mbfc3OjoYmJTn66EvNlyWLBuzSP/8NDX9VZFZ/WVbN2LXG1Lyt39HZ1Oe6igpX4EQYMHLwCgGXhzcQB+AANwdwQAAAABcQB+AAhzcQB+ABAAAAAAdXEAfgATAAAAQNfQMxCvOX52hOEEWHlJ5y9jDf3G5W8oJ4+EKTGyZ5P6LaE96mSUkeAy+RayXQj/dMfoVQ3PUpJo1hOVG3oKDZN4c3EAfgADcHcEAAAAAXEAfgAIc3IAFGphdmEudXRpbC5MaW5rZWRMaXN0DClTXUpgiCIDAAB4cHcEAAAAAXNxAH4AEAAAAAB1cQB+ABMAAABA+a4BMDXepp/Yq7Zq2c6sr05+vfClfr9UfQPJvVb2JUxxwqMwDK6Yx0lPmC7rvIRyY/QhGpDpd3x0R/j6Lv+1JXh4c3EAfgAQAAAAAnVxAH4AEwAAAEAq0ApxLAj1xziR6s91Jr6Lab1bfS78Blsyej45Z9RfdzPesiluXUiS9X2cAOQoX/l26NSdsm327cBa5ZBtN5oKc3IAD2phdmEudXRpbC5TdGFjaxD+KsK7CYYdAgAAeHIAEGphdmEudXRpbC5WZWN0b3LZl31bgDuvAQMAA0kAEWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50WwALZWxlbWVudERhdGF0ABNbTGphdmEvbGFuZy9PYmplY3Q7eHAAAAAAAAAAAHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAApwcHBwcHBwcHBweHNxAH4ADgAAAAB3BAAAAAB4c3EAfgAGAAAAAXNxAH4ACQAAAAAAAAACAAAAAgBzcQB+AA4AAAACdwQAAAACc3EAfgAQAAAAAHVxAH4AEwAAAEB2En5VL632VV7jcHU2LJUBhmskPMRAPCV7VcvE3ccvGlb0ZmILYHPCOxAxZ1LaVRhQD5t9ndbikSADksNLSgLbc3EAfgAQAAAAAXVxAH4AEwAAAECPum5ndel2cV0DO0aFdGHT6t2e+gLHiuPubkeYptsSHa5Y2KACLwWHalFF/io6Q+8bQ6F8FfZ1ulehH3ovi1KTeHNxAH4AA3B3BAAAAAB4c3EAfgADcHcEAAAAAXEAfgAIc3EAfgAbdwQAAAABc3EAfgAQAAAAAHVxAH4AEwAAAEBoBZPSL9+NarJmjWs54D74JIG5oYHAeSVnXWrNXo9sIjBIP9WDS8ibIUieo+OrGZ3BdVDjqt8Q4iBijK4gHD4neHhzcQB+ABAAAAACdXEAfgATAAAAQO7ns2zftOhxB7W/IQ/yMVUsWk92YrAej+9Vz1TL+Rub6LV03CZ0iZGh6nljsLW4TZM7PMir/pLh+9ddkZRpmSBzcQB+ACEAAAAAAAAAAHVxAH4AJQAAAApwcHBwcHBwcHBweHNxAH4ADgAAAAB3BAAAAAB4c3EAfgAGAAAAAnNxAH4ACQAAAAAAAAACAAAAAgBzcQB+AA4AAAACdwQAAAACc3EAfgAQAAAAAHVxAH4AEwAAAEBfn1I4frwrCieL8SjwpzloU+M/KX/FLFZZywQdiOk+De88uVVboatLeoQ+tvRv1ccBTyZ3p+XfZQr7h4a1bK2Xc3EAfgAQAAAAAXVxAH4AEwAAAEAqomdgnmkiTR+gQ4SJ/3c5LyODbsVjAE13wBZe/eC091mYsmo8TYf59GygFUyFlTLBbTTss9ErA7tPe9ksFBNWeHNxAH4AA3B3BAAAAAB4c3EAfgADcHcEAAAAAXEAfgAIc3EAfgAbdwQAAAABc3EAfgAQAAAAAHVxAH4AEwAAAEAuKca0h98qelKh+LmzFF/tBn1QFm76LHwr5/mPmiNQoJkRl6uiYBt88O71s9wyMBpXPOA81kysgximgMClKEeIeHhzcQB+ABAAAAACdXEAfgATAAAAQNJMSwSdsVx/lqjrF/Ks/2pXImcawCDcmP/7SN+Zm5FIejNs76EzHUN+Q5ftQsBEi3IvP+4EQlMqHMnzBs9jHLlzcQB+ACEAAAAAAAAAAHVxAH4AJQAAAApwcHBwcHBwcHBweHNxAH4ADgAAAAB3BAAAAAB4c3EAfgAGAAAAA3NxAH4ACQAAAAAAAAACAAAAAgBzcQB+AA4AAAACdwQAAAACc3EAfgAQAAAAAHVxAH4AEwAAAECf+hwClKCaF+EUbP0NQrkJr1P/EEjXFgueEqT7TqqqCcj+b+Fk9cVNPaIqQKQwXHFllm8fWke085Uq5xXN1OX2c3EAfgAQAAAAAXVxAH4AEwAAAEDAyjX4CVOTvCW+A90h/iw6b9/lUUnhm5t+pQOHjcQCZ4A51pRsz76IAqc6tYcVTlqK5lTSedHXl7jQQR2bAT+BeHNxAH4AA3B3BAAAAAB4c3EAfgADcHcEAAAAAXEAfgAIc3EAfgAbdwQAAAABc3EAfgAQAAAAAHVxAH4AEwAAAEBrbDCqCDIj/t84knP3pR30bInBUUIM82/KnVG80eZ4oFJ0rr+cLv1EZ2sHbCMdBI/HtEO+V+Ef1fCtpXUwh77+eHhzcQB+ABAAAAACdXEAfgATAAAAQAapdSARurABtVnQ5AAntsxOoCrZJ3V3T9LIwsq+2i6CwWtbfCn5ONBbl0doL5ESZcpUYHX8py53v8oo50SUxs9zcQB+ACEAAAAAAAAAAHVxAH4AJQAAAApwcHBwcHBwcHBweHNxAH4ADgAAAAB3BAAAAAB4c3EAfgAGAAAABHNxAH4ACQAAAAAAAAACAAAAAgBzcQB+AA4AAAACdwQAAAACc3EAfgAQAAAAAHVxAH4AEwAAAECyb+bl7wQR0LuLpGXFm/d2gqLne0nbBgvseAlLeItDeVfm9PnIV41/b8C0XE+OSewJStTBkSXiAUL0BFPm6qEJc3EAfgAQAAAAAXVxAH4AEwAAAEBpISb107v7VbCAlCnxSC1XIqQH8+wSc5i8EvF6Wfn/FFP92/Op6AbS8RWBAnk5ztm4k3rMVipTz8Uv8LDw7VDneHNxAH4AA3B3BAAAAAB4c3EAfgADcHcEAAAAAXEAfgAIc3EAfgAbdwQAAAABc3EAfgAQAAAAAHVxAH4AEwAAAEA3LQnVYbgs8aIcIHLxpBUIHQQf6t0pgKSDEF807/2rpyTsz2bbdP50Vjv5m6CI1j95jkbjXJg3Xt+exjlg3GaYeHhzcQB+ABAAAAACdXEAfgATAAAAQCfYCfWFAnSg+BuXuf0cMvtLPF93PYmdyTkGbUjiOsylGJz0Tz6RvnJHdK62lXmrsd7ZCj4D70fR3rWvD3Us4WpzcQB+ACEAAAAAAAAAAHVxAH4AJQAAAApwcHBwcHBwcHBweHNxAH4ADgAAAAB3BAAAAAB4eHg=");
+
     public void setUp()
     {
         if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
@@ -86,6 +91,20 @@
         }
     }
 
+    public void test160PrivateKeyRecovery()
+        throws Exception
+    {
+        KeyFactory kFact = KeyFactory.getInstance("XMSSMT", "BCPQC");
+
+        XMSSMTKey privKey = (XMSSMTKey)kFact.generatePrivate(new PKCS8EncodedKeySpec(priv160Pkcs8));
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(priv160Ser));
+
+        XMSSMTKey privKey2 = (XMSSMTKey)oIn.readObject();
+
+        assertEquals(privKey, privKey2);
+    }
+
     public void testPrivateKeyRecovery()
         throws Exception
     {
@@ -133,30 +152,26 @@
     {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
 
-        kpg.initialize(new XMSSMTParameterSpec(20, 10, XMSSMTParameterSpec.SHA256), new SecureRandom());
+        kpg.initialize(new XMSSMTParameterSpec(20, 2, XMSSMTParameterSpec.SHA256), new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHA256withXMSSMT-SHA256", "BCPQC");
+        StateAwareSignature sig = (StateAwareSignature)Signature.getInstance("XMSSMT-SHA256", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
+        sig.initSign(kp.getPrivate());
 
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
+        assertTrue(sig.isSigningCapable());
 
-        xmssSig.initSign(kp.getPrivate());
-
-        assertTrue(xmssSig.isSigningCapable());
-
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         byte[] s = sig.sign();
 
-        PrivateKey nKey = xmssSig.getUpdatedPrivateKey();
+        XMSSMTPrivateKey nKey = (XMSSMTPrivateKey)sig.getUpdatedPrivateKey();
 
-        assertFalse(kp.getPrivate().equals(nKey));
-        assertFalse(xmssSig.isSigningCapable());
+        assertTrue(kp.getPrivate().equals(nKey)); // key is mutable.
+        assertFalse(sig.isSigningCapable());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         try
         {
@@ -170,7 +185,7 @@
 
         try
         {
-            xmssSig.getUpdatedPrivateKey();
+            sig.getUpdatedPrivateKey();
             fail("no exception after key extraction");
         }
         catch (IllegalStateException e)
@@ -178,17 +193,32 @@
             assertEquals("signature object not in a signing state", e.getMessage());
         }
 
-        xmssSig.initSign(nKey);
+        XMSSMTPrivateKey singleUseKey = nKey.extractKeyShard(1);
+        sig.initSign(singleUseKey);
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         s = sig.sign();
 
-        xmssSig.initVerify(kp.getPublic());
+        sig.initVerify(kp.getPublic());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
-        assertTrue(xmssSig.verify(s));
+        assertTrue(sig.verify(s));
+
+        sig.initSign(singleUseKey);
+
+        sig.update(msg, 0, msg.length);
+
+        try
+        {
+            s = sig.sign();
+            fail("no exception");
+        }
+        catch (SignatureException e)
+        {
+            assertEquals("no usages of private key remaining", e.getMessage());
+        }
     }
 
     public void testXMSSMTSha256SignatureMultiple()
@@ -260,11 +290,11 @@
 
         assertEquals(20, privKey.getHeight());
         assertEquals(10, privKey.getLayers());
-        assertEquals(XMSSParameterSpec.SHA512, privKey.getTreeDigest());
+        assertEquals(XMSSMTParameterSpec.SHA512, privKey.getTreeDigest());
 
         assertEquals(20, pubKey.getHeight());
         assertEquals(10, pubKey.getLayers());
-        assertEquals(XMSSParameterSpec.SHA512, pubKey.getTreeDigest());
+        assertEquals(XMSSMTParameterSpec.SHA512, pubKey.getTreeDigest());
     }
 
     public void testXMSSMTSha256Signature()
@@ -278,21 +308,17 @@
 
         Signature sig = Signature.getInstance("SHA256withXMSSMT", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
+        sig.initSign(kp.getPrivate());
 
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
-
-        xmssSig.initSign(kp.getPrivate());
-
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         byte[] s = sig.sign();
 
-        xmssSig.initVerify(kp.getPublic());
+        sig.initVerify(kp.getPublic());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
-        assertTrue(xmssSig.verify(s));
+        assertTrue(sig.verify(s));
     }
 
     public void testXMSSMTSha512Signature()
@@ -306,21 +332,17 @@
 
         Signature sig = Signature.getInstance("SHA256withXMSSMT", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
+        sig.initSign(kp.getPrivate());
 
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
-
-        xmssSig.initSign(kp.getPrivate());
-
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         byte[] s = sig.sign();
 
-        xmssSig.initVerify(kp.getPublic());
+        sig.initVerify(kp.getPublic());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
-        assertTrue(xmssSig.verify(s));
+        assertTrue(sig.verify(s));
     }
 
     public void testXMSSMTShake128Signature()
@@ -332,23 +354,19 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHAKE128withXMSSMT-SHAKE128", "BCPQC");
+        Signature sig = Signature.getInstance("XMSSMT-SHAKE128", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
+        sig.initSign(kp.getPrivate());
 
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
-
-        xmssSig.initSign(kp.getPrivate());
-
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         byte[] s = sig.sign();
 
-        xmssSig.initVerify(kp.getPublic());
+        sig.initVerify(kp.getPublic());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
-        assertTrue(xmssSig.verify(s));
+        assertTrue(sig.verify(s));
     }
 
     public void testXMSSMTShake256Signature()
@@ -360,23 +378,19 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHAKE256withXMSSMT-SHAKE256", "BCPQC");
+        Signature sig = Signature.getInstance("XMSSMT-SHAKE256", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
+        sig.initSign(kp.getPrivate());
 
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
-
-        xmssSig.initSign(kp.getPrivate());
-
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
         byte[] s = sig.sign();
 
-        xmssSig.initVerify(kp.getPublic());
+        sig.initVerify(kp.getPublic());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.update(msg, 0, msg.length);
 
-        assertTrue(xmssSig.verify(s));
+        assertTrue(sig.verify(s));
     }
 
     public void testKeyRebuild()
@@ -384,26 +398,22 @@
     {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
 
-        kpg.initialize(new XMSSMTParameterSpec(6, 3, XMSSMTParameterSpec.SHA256), new SecureRandom());
+        kpg.initialize(new XMSSMTParameterSpec(20, 4, XMSSMTParameterSpec.SHA256), new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
         Signature sig = Signature.getInstance("SHA256withXMSSMT", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
-
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
-
-        xmssSig.initSign(kp.getPrivate());
+        sig.initSign(kp.getPrivate());
 
         for (int i = 0; i != 5; i++)
         {
-            xmssSig.update(msg, 0, msg.length);
+            sig.update(msg, 0, msg.length);
 
-            xmssSig.sign();
+            sig.sign();
         }
 
-        PrivateKey pKey = xmssSig.getUpdatedPrivateKey();
+        XMSSMTPrivateKey pKey = (XMSSMTPrivateKey)kp.getPrivate();
 
         PrivateKeyInfo pKeyInfo = PrivateKeyInfo.getInstance(pKey.getEncoded());
 
@@ -413,21 +423,23 @@
 
         // create a new PrivateKeyInfo containing a key with no BDS state.
         pKeyInfo = new PrivateKeyInfo(pKeyInfo.getPrivateKeyAlgorithm(),
-            new DERSequence(new ASN1Encodable[] { seq.getObjectAt(0), seq.getObjectAt(1) }));
+            new DERSequence(new ASN1Encodable[]{seq.getObjectAt(0), seq.getObjectAt(1)}));
 
-        XMSSMTKey privKey = (XMSSMTKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pKeyInfo.getEncoded()));
+        XMSSMTPrivateKey privKey = (XMSSMTPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pKeyInfo.getEncoded()));
 
-        xmssSig.initSign(pKey);
+        assertEquals(privKey.getIndex(), pKey.getIndex());
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.initSign(pKey);
 
-        byte[] sig1 = xmssSig.sign();
+        sig.update(msg, 0, msg.length);
 
-        xmssSig.initSign((PrivateKey)privKey);
+        byte[] sig1 = sig.sign();
 
-        xmssSig.update(msg, 0, msg.length);
+        sig.initSign(privKey);
 
-        byte[] sig2 = xmssSig.sign();
+        sig.update(msg, 0, msg.length);
+
+        byte[] sig2 = sig.sign();
 
         // make sure we get the same signature as the two keys should now
         // be in the same state.
@@ -439,7 +451,7 @@
     {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
 
-        kpg.initialize(new XMSSMTParameterSpec(10, 2, XMSSParameterSpec.SHA256), new SecureRandom());
+        kpg.initialize(new XMSSMTParameterSpec(20, 2, XMSSMTParameterSpec.SHA256), new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
@@ -450,11 +462,11 @@
         assertEquals(kp.getPrivate(), privKey);
 
         PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded()));
-
+   
         assertEquals(kp.getPublic(), pubKey);
 
-        assertEquals(10, privKey.getHeight());
-        assertEquals(XMSSParameterSpec.SHA256, privKey.getTreeDigest());
+        assertEquals(20, privKey.getHeight());
+        assertEquals(XMSSMTParameterSpec.SHA256, privKey.getTreeDigest());
 
         testSig("SHA256withXMSSMT", pubKey, (PrivateKey)privKey);
     }
@@ -507,7 +519,7 @@
 
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
 
-        kpg.initialize(new XMSSMTParameterSpec(4, 2,"SHA256"), new SecureRandom());
+        kpg.initialize(new XMSSMTParameterSpec(4, 2, "SHA256"), new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
@@ -538,6 +550,89 @@
         assertEquals(0, privKey.getUsagesRemaining());
     }
 
+    public void testNoRepeats()
+        throws Exception
+    {
+        byte[] message = Strings.toByteArray("hello, world!");
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
+
+        kpg.initialize(new XMSSMTParameterSpec(4, 2, "SHA256"), new SecureRandom());
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        XMSSMTPrivateKey privKey = (XMSSMTPrivateKey)kp.getPrivate();
+
+        Signature sigGen = Signature.getInstance(BCObjectIdentifiers.xmss_mt_SHA256.getId(), "BCPQC");
+        Signature sigVer = Signature.getInstance(BCObjectIdentifiers.xmss_mt_SHA256.getId(), "BCPQC");
+
+        Set sigs = new HashSet();
+        XMSSMTPrivateKey sigKey;
+        while (privKey.getUsagesRemaining() != 0)
+        {
+            sigKey = privKey.extractKeyShard(privKey.getUsagesRemaining() > 4 ? 4 : (int)privKey.getUsagesRemaining());
+            do
+            {
+                sigGen.initSign(sigKey);
+
+                sigGen.update(message);
+
+                byte[] sig = sigGen.sign();
+
+                sigVer.initVerify(kp.getPublic());
+
+                sigVer.update(message);
+
+                PQCSigUtils.SigWrapper sw = new PQCSigUtils.SigWrapper(sig);
+
+                if (sigs.contains(sw))
+                {
+                    fail("same sig generated twice");
+                }
+                sigs.add(sw);
+            }
+            while (sigKey.getUsagesRemaining() != 0);
+        }
+
+        kp = kpg.generateKeyPair();
+
+        privKey = (XMSSMTPrivateKey)kp.getPrivate();
+
+        sigs = new HashSet();
+
+        sigGen.initSign(privKey);
+
+        while (privKey.getUsagesRemaining() != 0)
+        {
+
+            sigGen.update(message);
+
+            byte[] sig = sigGen.sign();
+
+            sigVer.initVerify(kp.getPublic());
+
+            sigVer.update(message);
+
+            PQCSigUtils.SigWrapper sw = new PQCSigUtils.SigWrapper(sig);
+
+            if (sigs.contains(sw))
+            {
+                fail("same sig generated twice");
+            }
+            sigs.add(sw);
+        }
+
+        try
+        {
+            privKey.getIndex();
+            fail("no exception");
+        }
+        catch (IllegalStateException e)
+        {
+            assertEquals("key exhausted", e.getMessage());
+        }
+    }
+
     private void testPrehashAndWithoutPrehash(String baseAlgorithm, String digestName, Digest digest)
         throws Exception
     {
@@ -592,13 +687,87 @@
         assertTrue(s2.verify(sig));
     }
 
+    public void testShardedKeyExhaustion()
+        throws Exception
+    {
+        Signature s1 = Signature.getInstance(BCObjectIdentifiers.xmss_mt_SHA256.getId(), "BCPQC");
+        Signature s2 = Signature.getInstance(BCObjectIdentifiers.xmss_mt_SHA256.getId(), "BCPQC");
+
+        byte[] message = Strings.toByteArray("hello, world!");
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
+
+        kpg.initialize(new XMSSMTParameterSpec(4, 2, XMSSMTParameterSpec.SHA256), new SecureRandom());
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        XMSSMTPrivateKey privKey = (XMSSMTPrivateKey)kp.getPrivate();
+
+        assertEquals(16, privKey.getUsagesRemaining());
+
+        XMSSMTPrivateKey extPrivKey = privKey.extractKeyShard(4);
+
+        assertEquals(12, privKey.getUsagesRemaining());
+        assertEquals(4, extPrivKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, extPrivKey, 4);
+
+        assertEquals(12, privKey.getUsagesRemaining());
+
+        extPrivKey = privKey.extractKeyShard(4);
+
+        assertEquals(8, privKey.getUsagesRemaining());
+        assertEquals(4, extPrivKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, extPrivKey, 4);
+
+        assertEquals(8, privKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, privKey, 8);
+    }
+
+    private void exhaustKey(
+        Signature s1, Signature s2, byte[] message, KeyPair kp, XMSSMTPrivateKey extPrivKey, int usages)
+        throws GeneralSecurityException
+    {
+        // serialisation check
+        assertEquals(extPrivKey.getUsagesRemaining(), usages);
+        KeyFactory keyFact = KeyFactory.getInstance("XMSSMT", "BCPQC");
+
+        XMSSMTPrivateKey pKey = (XMSSMTPrivateKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(extPrivKey.getEncoded()));
+
+        assertEquals(usages, pKey.getUsagesRemaining());
+
+        // usage check
+        int count = 0;
+        do
+        {
+            s1.initSign(extPrivKey);
+
+            s1.update(message, 0, message.length);
+
+            byte[] sig = s1.sign();
+
+            s2.initVerify(kp.getPublic());
+
+            s2.update(message, 0, message.length);
+
+            assertTrue(s2.verify(sig));
+            count++;
+        }
+        while (extPrivKey.getUsagesRemaining() != 0);
+
+        assertEquals(usages, count);
+        assertEquals(0, extPrivKey.getUsagesRemaining());
+    }
+
     public void testReserialization()
         throws Exception
     {
         String digest = "SHA512";
-        String sigAlg = digest+"withXMSSMT";
+        String sigAlg = digest + "withXMSSMT";
         byte[] payload = Strings.toByteArray("Hello, world!");
-        
+
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSSMT", "BCPQC");
         kpg.initialize(new XMSSMTParameterSpec(4, 2, digest));
         KeyPair keyPair = kpg.generateKeyPair();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSTest.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSTest.java
index 8c7bffb..66dd321 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/test/XMSSTest.java
@@ -4,6 +4,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.security.GeneralSecurityException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -15,6 +16,8 @@
 import java.security.SignatureException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Set;
 
 import junit.framework.TestCase;
 import org.bouncycastle.asn1.ASN1Encodable;
@@ -84,6 +87,9 @@
             "6TkILd1jH6e5vP9Iwp+hANEWJdbxYX4gyyQQpudfOQ6+7xLJNaBEAmGsvLXJAJXu5NTICpC5LpKrWWxrz6tKRiLP10EBbxtLwM3wCW" +
             "6+d4CehmSP7B0ffx6AzJtD6l6T+lxyO0EMXG");
 
+    private static byte[] priv160Pkcs8 = Base64.decode("MIIMsAIBADAhBgorBgEEAYGwGgICMBMCAQACAQowCwYJYIZIAWUDBAIDBIIMhjCCDIICAQAwggELAgEBBEBDN/ZR2APXYlrHbvpt+Pr9kJ04g1DlfECqyYUpIWvCDfLA2vOOxbyGtXeRXkyp4rvZWecMQk8WR92gOhtKwHd1BECLEFvzguhVNshHOpOxEW5LuCXoZ9zTcQfLuuQHejFl5wxhRaCY5sYoaTQo9zEBy2iSzowlvRwMRvTNiBKKQfNZBECNYMDOjG3ZA34DLDjO/vc5aswoN82xWpg+C1U+QDq1O/xgYJpyHouVXme++Okldjn3iFuSu+7fOuQzhi24KwFfBEBxq4zDM+voog9eQscsyGEgocbeOxMD0y4XOhrQWZtt4kkwNSw1pHpGT2VqfS6HXwHJPfPt4zBEFSotYLd89q22oIILbASCC2is7QAFc3IAJG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEUwAAAAAAAAABAgAKSQAFaW5kZXhJAAFrSQAKdHJlZUhlaWdodFoABHVzZWRMABJhdXRoZW50aWNhdGlvblBhdGh0ABBMamF2YS91dGlsL0xpc3Q7TAAEa2VlcHQAD0xqYXZhL3V0aWwvTWFwO0wABnJldGFpbnEAfgACTAAEcm9vdHQAK0xvcmcvYm91bmN5Y2FzdGxlL3BxYy9jcnlwdG8veG1zcy9YTVNTTm9kZTtMAAVzdGFja3QAEUxqYXZhL3V0aWwvU3RhY2s7TAARdHJlZUhhc2hJbnN0YW5jZXNxAH4AAXhwAAAAAQAAAAIAAAAKAHNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAAKdwQAAAAKc3IAKW9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLlhNU1NOb2RlAAAAAAAAAAECAAJJAAZoZWlnaHRbAAV2YWx1ZXQAAltCeHAAAAAAdXIAAltCrPMX+AYIVOACAAB4cAAAAEB5FLmr7Fg9zYGpsR7YuhR2FM65AHNftemG+9dpkPt5lyDkxn+YOeZ3g9UF82HZn279mxJCjC45zqVEE8sNNjbmc3EAfgAIAAAAAXVxAH4ACwAAAEACObD6ZfiX6zsKt0SMrwDO7bl1qO4kQuiJxc3tzmLwcTXOjVkx7JNEMOuzU22l4M2ciw2oto/udxSOv3XBeNcTc3EAfgAIAAAAAnVxAH4ACwAAAECkIOT5Q+vggGnvXoRZ4+/7fG05jd/maC056uaHeGbbPfJw4unrOwQmEHtoW1yQW2FwIVWCDkygE7M3h3pt0ATHc3EAfgAIAAAAA3VxAH4ACwAAAEA9TrshpaOEu+m+sNxGm3YHtBfhA4Py+OIBmxPBZcXAn0GwzPcV5rSiALUaYY9X9s4aFTOhc4Q7kLnKwlChNoFIc3EAfgAIAAAABHVxAH4ACwAAAEDKYWaVj4aT5U9RCUm+wCdezT45wyvDlo3Q5HyncgCTbYE62V3J+F2BM/KK35KbzxE1fO7JuaZEUwH98JrnHBgMc3EAfgAIAAAABXVxAH4ACwAAAEAp+f42Vo7p2LGi5TmD9Mm5XgRiIwtpwJeJSkz5uHR0/JZXcWg9CzaMWIMq6xoISCAFtAlzRbcMJPDTRZkju/Mrc3EAfgAIAAAABnVxAH4ACwAAAEC497rUHBSmaZ4KzHtTj1LzHbkzdHP0wl4UZDDP/CVfCJuQbxG6jk7GeX8Q80Hgjn19pClLm9WmZpgrl/p2N/54c3EAfgAIAAAAB3VxAH4ACwAAAECPfIzlWQUDJXqTO1u4xl5fHo3tXbfgc7YAM+R0/SR0KHOJxt0nSWDLakn5/1h0Px436iplZi3XgF+rfa9DrEsQc3EAfgAIAAAACHVxAH4ACwAAAEDu1DYluPI72Q1B6KigZDMRYdaz/1JD5Pzcv8zOJfabJdrHCQsMbBAfdtFaKLURaxPSEsf1gCcc2EdwvZT27+1Vc3EAfgAIAAAACXVxAH4ACwAAAECTi7pmtl1nNHXZWX6wTAlYSatU8MSNael/mk8FZlGiKuGaUVRVhKyjs4EeQpfaLxR+VMuwAfadPNdDIkH72qaUeHNyABFqYXZhLnV0aWwuVHJlZU1hcAzB9j4tJWrmAwABTAAKY29tcGFyYXRvcnQAFkxqYXZhL3V0aWwvQ29tcGFyYXRvcjt4cHB3BAAAAAFzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAHNxAH4ACAAAAAB1cQB+AAsAAABAig3XjYq59uxihUmXtU+aTe940TeN7uT+DaYAF+O7Vx7NyRkDxLNVoAEFsfyooFGrST2c6ccbiUey7CvtCdPxx3hzcQB+AB9wdwQAAAABc3EAfgAiAAAACHNyABRqYXZhLnV0aWwuTGlua2VkTGlzdAwpU11KYIgiAwAAeHB3BAAAAAFzcQB+AAgAAAAIdXEAfgALAAAAQIRTdkkkqYALwdLqnMBo4qhqXERBO382BrU5XYQccYbjKVCaXSi0hwN/N2f1Fcq/YuDOEFF97b3WzE8Ab6qGPCF4eHNxAH4ACAAAAAp1cQB+AAsAAABAcauMwzPr6KIPXkLHLMhhIKHG3jsTA9MuFzoa0FmbbeJJMDUsNaR6Rk9lan0uh18ByT3z7eMwRBUqLWC3fPattnNyAA9qYXZhLnV0aWwuU3RhY2sQ/irCuwmGHQIAAHhyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJABFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAAAAAAAB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAKcHBwcHBwcHBwcHhzcQB+AAYAAAAIdwQAAAAIc3IALG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEU1RyZWVIYXNoAAAAAAAAAAECAAZaAAhmaW5pc2hlZEkABmhlaWdodEkADWluaXRpYWxIZWlnaHRaAAtpbml0aWFsaXplZEkACW5leHRJbmRleEwACHRhaWxOb2RlcQB+AAN4cAEAAAAAAAAAAAAAAAAAc3EAfgAIAAAAAHVxAH4ACwAAAECFctbzECC6ZrFZe+UnM95s/Ums9BJP7J9NTKjy3+W9r4PDKcPGAa/B+uZOqKI/0pVxYhwBW2BaNHO0y4UKLdZtc3EAfgA2AQAAAAEAAAABAAAAAABzcQB+AAgAAAABdXEAfgALAAAAQBYBEjm2/yu2OZCNhquulCNxzTyxiBRZK7DFqKpT30XPaWhNUlvdvru29ANYHZQEzomCu4yq0HIbcjqfEHqWlaNzcQB+ADYBAAAAAgAAAAIAAAAAAHNxAH4ACAAAAAJ1cQB+AAsAAABA/AfZ9FGm3d6NdZCKTePe+tI4nPFapgu5dRRNZ6pTXZVx5xwrU4NOxpdYTEFAtePwUY0m2qXz0FV5t4a/C7B4j3NxAH4ANgEAAAADAAAAAwAAAAAAc3EAfgAIAAAAA3VxAH4ACwAAAEDMEzR2G1VxbHoVC+FEqWD+Bs+jcHVyrxKhvahbVV4qHMqkJwylprJJxv5G9tqFYkPkONe2KKGTA7fsOHmJ0TtWc3EAfgA2AQAAAAQAAAAEAAAAAABzcQB+AAgAAAAEdXEAfgALAAAAQPE1QqVKlZVafWBIVtEOkdc/AJhuqYTf77nItVJRmSq7MgQqTW2T6wsPiwtE4kQkRsT8ye09mlUdCjuK7sooJAZzcQB+ADYBAAAABQAAAAUAAAAAAHNxAH4ACAAAAAV1cQB+AAsAAABAZBJfqNPApebvBzLRDOWkxO+ybrTnnmj+LkmPySVnxagopZVrs+TvAdv6/DwTcpA/UC1PDwey0xGy6Pcz0afgwnNxAH4ANgEAAAAGAAAABgAAAAAAc3EAfgAIAAAABnVxAH4ACwAAAEDEDe5X6TptLGua5gWG74ncmI7vtsjMDNjxdZG6M+KGS7gY9nnvdMlZ6NWeFu4J5C0rSrs+9XWubh0JV8QyDOLqc3EAfgA2AQAAAAcAAAAHAAAAAABzcQB+AAgAAAAHdXEAfgALAAAAQN9VTJZMOErehOxkbLLVW/CSNUuRePd1MuGl70J8BvNqmInRfO8EHBOLlTcwulbgkE9naTQgqcmf26HWGI+IQSp4");
+    private static byte[] priv160Ser = Base64.decode("rO0ABXNyADpvcmcuYm91bmN5Y2FzdGxlLnBxYy5qY2FqY2UucHJvdmlkZXIueG1zcy5CQ1hNU1NQcml2YXRlS2V5duokzxWSCVIDAAB4cHVyAAJbQqzzF/gGCFTgAgAAeHAAAAy0MIIMsAIBADAhBgorBgEEAYGwGgICMBMCAQACAQowCwYJYIZIAWUDBAIDBIIMhjCCDIICAQAwggELAgEBBEBDN/ZR2APXYlrHbvpt+Pr9kJ04g1DlfECqyYUpIWvCDfLA2vOOxbyGtXeRXkyp4rvZWecMQk8WR92gOhtKwHd1BECLEFvzguhVNshHOpOxEW5LuCXoZ9zTcQfLuuQHejFl5wxhRaCY5sYoaTQo9zEBy2iSzowlvRwMRvTNiBKKQfNZBECNYMDOjG3ZA34DLDjO/vc5aswoN82xWpg+C1U+QDq1O/xgYJpyHouVXme++Okldjn3iFuSu+7fOuQzhi24KwFfBEBxq4zDM+voog9eQscsyGEgocbeOxMD0y4XOhrQWZtt4kkwNSw1pHpGT2VqfS6HXwHJPfPt4zBEFSotYLd89q22oIILbASCC2is7QAFc3IAJG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEUwAAAAAAAAABAgAKSQAFaW5kZXhJAAFrSQAKdHJlZUhlaWdodFoABHVzZWRMABJhdXRoZW50aWNhdGlvblBhdGh0ABBMamF2YS91dGlsL0xpc3Q7TAAEa2VlcHQAD0xqYXZhL3V0aWwvTWFwO0wABnJldGFpbnEAfgACTAAEcm9vdHQAK0xvcmcvYm91bmN5Y2FzdGxlL3BxYy9jcnlwdG8veG1zcy9YTVNTTm9kZTtMAAVzdGFja3QAEUxqYXZhL3V0aWwvU3RhY2s7TAARdHJlZUhhc2hJbnN0YW5jZXNxAH4AAXhwAAAAAQAAAAIAAAAKAHNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAAKdwQAAAAKc3IAKW9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLlhNU1NOb2RlAAAAAAAAAAECAAJJAAZoZWlnaHRbAAV2YWx1ZXQAAltCeHAAAAAAdXIAAltCrPMX+AYIVOACAAB4cAAAAEB5FLmr7Fg9zYGpsR7YuhR2FM65AHNftemG+9dpkPt5lyDkxn+YOeZ3g9UF82HZn279mxJCjC45zqVEE8sNNjbmc3EAfgAIAAAAAXVxAH4ACwAAAEACObD6ZfiX6zsKt0SMrwDO7bl1qO4kQuiJxc3tzmLwcTXOjVkx7JNEMOuzU22l4M2ciw2oto/udxSOv3XBeNcTc3EAfgAIAAAAAnVxAH4ACwAAAECkIOT5Q+vggGnvXoRZ4+/7fG05jd/maC056uaHeGbbPfJw4unrOwQmEHtoW1yQW2FwIVWCDkygE7M3h3pt0ATHc3EAfgAIAAAAA3VxAH4ACwAAAEA9TrshpaOEu+m+sNxGm3YHtBfhA4Py+OIBmxPBZcXAn0GwzPcV5rSiALUaYY9X9s4aFTOhc4Q7kLnKwlChNoFIc3EAfgAIAAAABHVxAH4ACwAAAEDKYWaVj4aT5U9RCUm+wCdezT45wyvDlo3Q5HyncgCTbYE62V3J+F2BM/KK35KbzxE1fO7JuaZEUwH98JrnHBgMc3EAfgAIAAAABXVxAH4ACwAAAEAp+f42Vo7p2LGi5TmD9Mm5XgRiIwtpwJeJSkz5uHR0/JZXcWg9CzaMWIMq6xoISCAFtAlzRbcMJPDTRZkju/Mrc3EAfgAIAAAABnVxAH4ACwAAAEC497rUHBSmaZ4KzHtTj1LzHbkzdHP0wl4UZDDP/CVfCJuQbxG6jk7GeX8Q80Hgjn19pClLm9WmZpgrl/p2N/54c3EAfgAIAAAAB3VxAH4ACwAAAECPfIzlWQUDJXqTO1u4xl5fHo3tXbfgc7YAM+R0/SR0KHOJxt0nSWDLakn5/1h0Px436iplZi3XgF+rfa9DrEsQc3EAfgAIAAAACHVxAH4ACwAAAEDu1DYluPI72Q1B6KigZDMRYdaz/1JD5Pzcv8zOJfabJdrHCQsMbBAfdtFaKLURaxPSEsf1gCcc2EdwvZT27+1Vc3EAfgAIAAAACXVxAH4ACwAAAECTi7pmtl1nNHXZWX6wTAlYSatU8MSNael/mk8FZlGiKuGaUVRVhKyjs4EeQpfaLxR+VMuwAfadPNdDIkH72qaUeHNyABFqYXZhLnV0aWwuVHJlZU1hcAzB9j4tJWrmAwABTAAKY29tcGFyYXRvcnQAFkxqYXZhL3V0aWwvQ29tcGFyYXRvcjt4cHB3BAAAAAFzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAHNxAH4ACAAAAAB1cQB+AAsAAABAig3XjYq59uxihUmXtU+aTe940TeN7uT+DaYAF+O7Vx7NyRkDxLNVoAEFsfyooFGrST2c6ccbiUey7CvtCdPxx3hzcQB+AB9wdwQAAAABc3EAfgAiAAAACHNyABRqYXZhLnV0aWwuTGlua2VkTGlzdAwpU11KYIgiAwAAeHB3BAAAAAFzcQB+AAgAAAAIdXEAfgALAAAAQIRTdkkkqYALwdLqnMBo4qhqXERBO382BrU5XYQccYbjKVCaXSi0hwN/N2f1Fcq/YuDOEFF97b3WzE8Ab6qGPCF4eHNxAH4ACAAAAAp1cQB+AAsAAABAcauMwzPr6KIPXkLHLMhhIKHG3jsTA9MuFzoa0FmbbeJJMDUsNaR6Rk9lan0uh18ByT3z7eMwRBUqLWC3fPattnNyAA9qYXZhLnV0aWwuU3RhY2sQ/irCuwmGHQIAAHhyABBqYXZhLnV0aWwuVmVjdG9y2Zd9W4A7rwEDAANJABFjYXBhY2l0eUluY3JlbWVudEkADGVsZW1lbnRDb3VudFsAC2VsZW1lbnREYXRhdAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAAAAAAAB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAAKcHBwcHBwcHBwcHhzcQB+AAYAAAAIdwQAAAAIc3IALG9yZy5ib3VuY3ljYXN0bGUucHFjLmNyeXB0by54bXNzLkJEU1RyZWVIYXNoAAAAAAAAAAECAAZaAAhmaW5pc2hlZEkABmhlaWdodEkADWluaXRpYWxIZWlnaHRaAAtpbml0aWFsaXplZEkACW5leHRJbmRleEwACHRhaWxOb2RlcQB+AAN4cAEAAAAAAAAAAAAAAAAAc3EAfgAIAAAAAHVxAH4ACwAAAECFctbzECC6ZrFZe+UnM95s/Ums9BJP7J9NTKjy3+W9r4PDKcPGAa/B+uZOqKI/0pVxYhwBW2BaNHO0y4UKLdZtc3EAfgA2AQAAAAEAAAABAAAAAABzcQB+AAgAAAABdXEAfgALAAAAQBYBEjm2/yu2OZCNhquulCNxzTyxiBRZK7DFqKpT30XPaWhNUlvdvru29ANYHZQEzomCu4yq0HIbcjqfEHqWlaNzcQB+ADYBAAAAAgAAAAIAAAAAAHNxAH4ACAAAAAJ1cQB+AAsAAABA/AfZ9FGm3d6NdZCKTePe+tI4nPFapgu5dRRNZ6pTXZVx5xwrU4NOxpdYTEFAtePwUY0m2qXz0FV5t4a/C7B4j3NxAH4ANgEAAAADAAAAAwAAAAAAc3EAfgAIAAAAA3VxAH4ACwAAAEDMEzR2G1VxbHoVC+FEqWD+Bs+jcHVyrxKhvahbVV4qHMqkJwylprJJxv5G9tqFYkPkONe2KKGTA7fsOHmJ0TtWc3EAfgA2AQAAAAQAAAAEAAAAAABzcQB+AAgAAAAEdXEAfgALAAAAQPE1QqVKlZVafWBIVtEOkdc/AJhuqYTf77nItVJRmSq7MgQqTW2T6wsPiwtE4kQkRsT8ye09mlUdCjuK7sooJAZzcQB+ADYBAAAABQAAAAUAAAAAAHNxAH4ACAAAAAV1cQB+AAsAAABAZBJfqNPApebvBzLRDOWkxO+ybrTnnmj+LkmPySVnxagopZVrs+TvAdv6/DwTcpA/UC1PDwey0xGy6Pcz0afgwnNxAH4ANgEAAAAGAAAABgAAAAAAc3EAfgAIAAAABnVxAH4ACwAAAEDEDe5X6TptLGua5gWG74ncmI7vtsjMDNjxdZG6M+KGS7gY9nnvdMlZ6NWeFu4J5C0rSrs+9XWubh0JV8QyDOLqc3EAfgA2AQAAAAcAAAAHAAAAAABzcQB+AAgAAAAHdXEAfgALAAAAQN9VTJZMOErehOxkbLLVW/CSNUuRePd1MuGl70J8BvNqmInRfO8EHBOLlTcwulbgkE9naTQgqcmf26HWGI+IQSp4eA==");
+
     public void setUp()
     {
         if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null)
@@ -92,6 +98,20 @@
         }
     }
 
+    public void test160PrivateKeyRecovery()
+        throws Exception
+    {
+        KeyFactory kFact = KeyFactory.getInstance("XMSS", "BCPQC");
+
+        XMSSKey privKey = (XMSSKey)kFact.generatePrivate(new PKCS8EncodedKeySpec(priv160Pkcs8));
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(priv160Ser));
+
+        XMSSKey privKey2 = (XMSSKey)oIn.readObject();
+
+        assertEquals(privKey, privKey2);
+    }
+
     public void testPrivateKeyRecovery()
         throws Exception
     {
@@ -143,17 +163,13 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHA256withXMSS", "BCPQC");
-
-        assertTrue(sig instanceof StateAwareSignature);
-
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
+        Signature xmssSig = Signature.getInstance("SHA256withXMSS", "BCPQC");
 
         xmssSig.initSign(kp.getPrivate());
 
         xmssSig.update(msg, 0, msg.length);
 
-        byte[] s = sig.sign();
+        byte[] s = xmssSig.sign();
 
         xmssSig.initVerify(kp.getPublic());
 
@@ -171,17 +187,13 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHA512withXMSS", "BCPQC");
-
-        assertTrue(sig instanceof StateAwareSignature);
-
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
+        Signature xmssSig = Signature.getInstance("SHA512withXMSS", "BCPQC");
 
         xmssSig.initSign(kp.getPrivate());
 
         xmssSig.update(msg, 0, msg.length);
 
-        byte[] s = sig.sign();
+        byte[] s = xmssSig.sign();
 
         xmssSig.initVerify(kp.getPublic());
 
@@ -199,17 +211,13 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHAKE128withXMSS", "BCPQC");
-
-        assertTrue(sig instanceof StateAwareSignature);
-
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
+        Signature xmssSig = Signature.getInstance("SHAKE128withXMSS", "BCPQC");
 
         xmssSig.initSign(kp.getPrivate());
 
         xmssSig.update(msg, 0, msg.length);
 
-        byte[] s = sig.sign();
+        byte[] s = xmssSig.sign();
 
         xmssSig.initVerify(kp.getPublic());
 
@@ -227,17 +235,13 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        Signature sig = Signature.getInstance("SHAKE256withXMSS", "BCPQC");
-
-        assertTrue(sig instanceof StateAwareSignature);
-
-        StateAwareSignature xmssSig = (StateAwareSignature)sig;
+        Signature xmssSig = Signature.getInstance("SHAKE256withXMSS", "BCPQC");
 
         xmssSig.initSign(kp.getPrivate());
 
         xmssSig.update(msg, 0, msg.length);
 
-        byte[] s = sig.sign();
+        byte[] s = xmssSig.sign();
 
         xmssSig.initVerify(kp.getPublic());
 
@@ -246,7 +250,7 @@
         assertTrue(xmssSig.verify(s));
     }
 
-    public void testXMSSSha256SignatureMultiple()
+    public void testXMSSSha256SignatureMultiplePreHash()
         throws Exception
     {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSS", "BCPQC");
@@ -255,17 +259,19 @@
 
         KeyPair kp = kpg.generateKeyPair();
 
-        StateAwareSignature sig1 = (StateAwareSignature)Signature.getInstance("SHA256withXMSS", "BCPQC");
+        Signature sig1 = Signature.getInstance("SHA256withXMSS", "BCPQC");
 
-        StateAwareSignature sig2 = (StateAwareSignature)Signature.getInstance("SHA256withXMSS", "BCPQC");
+        Signature sig2 = Signature.getInstance("SHA256withXMSS", "BCPQC");
 
-        StateAwareSignature sig3 = (StateAwareSignature)Signature.getInstance("SHA256withXMSS", "BCPQC");
+        Signature sig3 = Signature.getInstance("SHA256withXMSS", "BCPQC");
 
-        sig1.initSign(kp.getPrivate());
+        XMSSPrivateKey xmsPrivKey = (XMSSPrivateKey)kp.getPrivate();
 
-        sig2.initSign(sig1.getUpdatedPrivateKey());
+        sig1.initSign(xmsPrivKey.extractKeyShard(1));
 
-        sig3.initSign(sig2.getUpdatedPrivateKey());
+        sig2.initSign(xmsPrivKey.extractKeyShard(1));
+
+        sig3.initSign(xmsPrivKey.extractKeyShard(1));
 
         sig1.update(msg, 0, msg.length);
 
@@ -299,7 +305,7 @@
     {
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSS", "BCPQC");
 
-        kpg.initialize(new XMSSParameterSpec(10, XMSSParameterSpec.SHA256), new SecureRandom());
+        kpg.initialize(XMSSParameterSpec.SHA2_10_256, new SecureRandom());
 
         KeyPair kp = kpg.generateKeyPair();
 
@@ -316,7 +322,7 @@
         assertEquals(10, privKey.getHeight());
         assertEquals(XMSSParameterSpec.SHA256, privKey.getTreeDigest());
 
-        testSig("SHA256withXMSS", pubKey, (PrivateKey)privKey);
+        testSig("XMSS", pubKey, (PrivateKey)privKey);
     }
 
     public void testXMSSSha512KeyFactory()
@@ -428,8 +434,6 @@
 
         Signature sig = Signature.getInstance("SHA256withXMSS", "BCPQC");
 
-        assertTrue(sig instanceof StateAwareSignature);
-
         StateAwareSignature xmssSig = (StateAwareSignature)sig;
 
         xmssSig.initSign(kp.getPrivate());
@@ -442,7 +446,7 @@
 
         PrivateKey nKey = xmssSig.getUpdatedPrivateKey();
 
-        assertFalse(kp.getPrivate().equals(nKey));
+        assertTrue(kp.getPrivate().equals(nKey));
         assertFalse(xmssSig.isSigningCapable());
 
         xmssSig.update(msg, 0, msg.length);
@@ -590,6 +594,163 @@
         assertEquals(0, privKey.getUsagesRemaining());
     }
 
+    public void testShardedKeyExhaustion()
+        throws Exception
+    {
+        Signature s1 = Signature.getInstance(BCObjectIdentifiers.xmss_SHA256.getId(), "BCPQC");
+        Signature s2 = Signature.getInstance(BCObjectIdentifiers.xmss_SHA256.getId(), "BCPQC");
+
+        byte[] message = Strings.toByteArray("hello, world!");
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSS", "BCPQC");
+
+        kpg.initialize(new XMSSParameterSpec(4, "SHA256"), new SecureRandom());
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        XMSSPrivateKey privKey = (XMSSPrivateKey)kp.getPrivate();
+
+        assertEquals(16, privKey.getUsagesRemaining());
+
+        XMSSPrivateKey extPrivKey = privKey.extractKeyShard(4);
+
+        assertEquals(12, privKey.getUsagesRemaining());
+        assertEquals(4, extPrivKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, extPrivKey, 4);
+
+        assertEquals(12, privKey.getUsagesRemaining());
+
+        extPrivKey = privKey.extractKeyShard(4);
+
+        assertEquals(8, privKey.getUsagesRemaining());
+        assertEquals(4, extPrivKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, extPrivKey, 4);
+
+        assertEquals(8, privKey.getUsagesRemaining());
+
+        exhaustKey(s1, s2, message, kp, privKey, 8);
+    }
+
+    private void exhaustKey(
+        Signature s1, Signature s2, byte[] message, KeyPair kp, XMSSPrivateKey extPrivKey, int usages)
+        throws GeneralSecurityException
+    {
+        // serialisation check
+        assertEquals(extPrivKey.getUsagesRemaining(), usages);
+        KeyFactory keyFact = KeyFactory.getInstance("XMSS", "BCPQC");
+
+        XMSSPrivateKey pKey = (XMSSPrivateKey)keyFact.generatePrivate(new PKCS8EncodedKeySpec(extPrivKey.getEncoded()));
+
+        assertEquals(usages, pKey.getUsagesRemaining());
+
+        // usage check
+        int count = 0;
+        do
+        {
+            s1.initSign(extPrivKey);
+
+            s1.update(message, 0, message.length);
+
+            byte[] sig = s1.sign();
+
+            s2.initVerify(kp.getPublic());
+
+            s2.update(message, 0, message.length);
+
+            assertTrue(s2.verify(sig));
+            count++;
+        }
+        while (extPrivKey.getUsagesRemaining() != 0);
+
+        assertEquals(usages, count);
+        assertEquals(0, extPrivKey.getUsagesRemaining());
+    }
+
+    public void testNoRepeats()
+        throws Exception
+    {
+        byte[] message = Strings.toByteArray("hello, world!");
+
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XMSS", "BCPQC");
+
+        kpg.initialize(new XMSSParameterSpec(4, "SHA256"), new SecureRandom());
+
+        KeyPair kp = kpg.generateKeyPair();
+
+        XMSSPrivateKey privKey = (XMSSPrivateKey)kp.getPrivate();
+
+        Signature sigGen = Signature.getInstance(BCObjectIdentifiers.xmss_SHA256.getId(), "BCPQC");
+        Signature sigVer = Signature.getInstance(BCObjectIdentifiers.xmss_SHA256.getId(), "BCPQC");
+
+        Set sigs = new HashSet();
+        XMSSPrivateKey sigKey;
+        while (privKey.getUsagesRemaining() != 0)
+        {
+            sigKey = privKey.extractKeyShard(privKey.getUsagesRemaining() > 4 ? 4 : (int)privKey.getUsagesRemaining());
+            do
+            {
+                sigGen.initSign(sigKey);
+
+                sigGen.update(message);
+
+                byte[] sig = sigGen.sign();
+
+                sigVer.initVerify(kp.getPublic());
+
+                sigVer.update(message);
+
+                PQCSigUtils.SigWrapper sw = new PQCSigUtils.SigWrapper(sig);
+
+                if (sigs.contains(sw))
+                {
+                    fail("same sig generated twice");
+                }
+                sigs.add(sw);
+            }
+            while (sigKey.getUsagesRemaining() != 0);
+        }
+
+        kp = kpg.generateKeyPair();
+
+        privKey = (XMSSPrivateKey)kp.getPrivate();
+
+        sigs = new HashSet();
+
+        sigGen.initSign(privKey);
+
+        while (privKey.getUsagesRemaining() != 0)
+        {
+
+            sigGen.update(message);
+
+            byte[] sig = sigGen.sign();
+
+            sigVer.initVerify(kp.getPublic());
+
+            sigVer.update(message);
+
+            PQCSigUtils.SigWrapper sw = new PQCSigUtils.SigWrapper(sig);
+
+            if (sigs.contains(sw))
+            {
+                fail("same sig generated twice");
+            }
+            sigs.add(sw);
+        }
+        
+        try
+        {
+            privKey.getIndex();
+            fail("no exception");
+        }
+        catch (IllegalStateException e)
+        {
+            assertEquals("key exhausted", e.getMessage());
+        }
+    }
+
     private void testPrehashAndWithoutPrehash(String baseAlgorithm, String digestName, Digest digest)
         throws Exception
     {
@@ -660,19 +821,31 @@
 
         for (int i = 0; i != 10; i++)
         {
-            StateAwareSignature signer = (StateAwareSignature)Signature.getInstance(sigAlg, "BCPQC");
+            Signature signer = Signature.getInstance(sigAlg, "BCPQC");
             signer.initSign(privateKey);
             signer.update(payload);
 
             byte[] signature = signer.sign();
 
             // serialise private key
-            byte[] enc = signer.getUpdatedPrivateKey().getEncoded();
+            byte[] enc = privateKey.getEncoded();
             privateKey = KeyFactory.getInstance("XMSS").generatePrivate(new PKCS8EncodedKeySpec(enc));
             Signature verifier = Signature.getInstance(sigAlg, "BCPQC");
             verifier.initVerify(publicKey);
             verifier.update(payload);
             assertTrue(verifier.verify(signature));
         }
+
+        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+        ObjectOutputStream oOut = new ObjectOutputStream(bOut);
+
+        oOut.writeObject(privateKey);
+        oOut.writeObject(privateKey);
+        oOut.close();
+
+        ObjectInputStream oIn = new ObjectInputStream(new ByteArrayInputStream(bOut.toByteArray()));
+
+        oIn.readObject();
+        oIn.readObject();
     }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java
index 7748f53..3f4fa46 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPrivateKey.java
@@ -48,6 +48,16 @@
         this.keyParams = (XMSSMTPrivateKeyParameters)PrivateKeyFactory.createKey(keyInfo);
     }
 
+    public long getIndex()
+    {
+        if (getUsagesRemaining() == 0)
+        {
+            throw new IllegalStateException("key exhausted");
+        }
+
+        return keyParams.getIndex();
+    }
+
     public long getUsagesRemaining()
     {
         return keyParams.getUsagesRemaining();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPublicKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPublicKey.java
index 7c943c1..cc557c1 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSMTPublicKey.java
@@ -8,7 +8,6 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.pqc.asn1.XMSSMTKeyParams;
 import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
 import org.bouncycastle.pqc.crypto.util.SubjectPublicKeyInfoFactory;
 import org.bouncycastle.pqc.crypto.xmss.XMSSMTPublicKeyParameters;
@@ -38,9 +37,8 @@
     private void init(SubjectPublicKeyInfo keyInfo)
         throws IOException
     {
-        XMSSMTKeyParams keyParams = XMSSMTKeyParams.getInstance(keyInfo.getAlgorithm().getParameters());
-        this.treeDigest = keyParams.getTreeDigest().getAlgorithm();
         this.keyParams = (XMSSMTPublicKeyParameters)PublicKeyFactory.createKey(keyInfo);
+        this.treeDigest =  DigestUtil.getDigestOID(this.keyParams.getTreeDigest());
     }
 
     public boolean equals(Object o)
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java
index da9adc3..994f828 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPrivateKey.java
@@ -48,6 +48,15 @@
         this.keyParams = (XMSSPrivateKeyParameters)PrivateKeyFactory.createKey(keyInfo);
     }
 
+    public long getIndex()
+    {
+        if (getUsagesRemaining() == 0)
+        {
+            throw new IllegalStateException("key exhausted");
+        }
+        return keyParams.getIndex();
+    }
+
     public long getUsagesRemaining()
     {
         return keyParams.getUsagesRemaining();
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPublicKey.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPublicKey.java
index 35e74c6..a4b4771 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPublicKey.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/BCXMSSPublicKey.java
@@ -8,7 +8,6 @@
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.crypto.CipherParameters;
-import org.bouncycastle.pqc.asn1.XMSSKeyParams;
 import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
 import org.bouncycastle.pqc.crypto.util.SubjectPublicKeyInfoFactory;
 import org.bouncycastle.pqc.crypto.xmss.XMSSPublicKeyParameters;
@@ -40,9 +39,8 @@
     private void init(SubjectPublicKeyInfo keyInfo)
         throws IOException
     {
-        XMSSKeyParams keyParams = XMSSKeyParams.getInstance(keyInfo.getAlgorithm().getParameters());
-        this.treeDigest = keyParams.getTreeDigest().getAlgorithm();
         this.keyParams = (XMSSPublicKeyParameters)PublicKeyFactory.createKey(keyInfo);
+        this.treeDigest = DigestUtil.getDigestOID(keyParams.getTreeDigest());
     }
 
     /**
@@ -87,7 +85,14 @@
         {
             BCXMSSPublicKey otherKey = (BCXMSSPublicKey)o;
 
-            return treeDigest.equals(otherKey.treeDigest) && Arrays.areEqual(keyParams.toByteArray(), otherKey.keyParams.toByteArray());
+            try
+            {
+                return treeDigest.equals(otherKey.treeDigest) && Arrays.areEqual(keyParams.getEncoded(), otherKey.keyParams.getEncoded());
+            }
+            catch (IOException e)
+            {
+                return false;
+            }
         }
 
         return false;
@@ -95,7 +100,15 @@
 
     public int hashCode()
     {
-        return treeDigest.hashCode() + 37 * Arrays.hashCode(keyParams.toByteArray());
+        try
+        {
+            return treeDigest.hashCode() + 37 * Arrays.hashCode(keyParams.getEncoded());
+        }
+        catch (IOException e)
+        {
+            // should never happen, but...
+            return treeDigest.hashCode();
+        }
     }
 
     public int getHeight()
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/DigestUtil.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/DigestUtil.java
index 95beb30..eb43a28 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/DigestUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/DigestUtil.java
@@ -33,6 +33,28 @@
         throw new IllegalArgumentException("unrecognized digest OID: " + oid);
     }
 
+    static ASN1ObjectIdentifier getDigestOID(String digest)
+    {
+        if (digest.equals("SHA-256"))
+        {
+            return NISTObjectIdentifiers.id_sha256;
+        }
+        if (digest.equals("SHA-512"))
+        {
+            return NISTObjectIdentifiers.id_sha512;
+        }
+        if (digest.equals("SHAKE128"))
+        {
+            return NISTObjectIdentifiers.id_shake128;
+        }
+        if (digest.equals("SHAKE256"))
+        {
+            return NISTObjectIdentifiers.id_shake256;
+        }
+
+        throw new IllegalArgumentException("unrecognized digest: " + digest);
+    }
+
     public static byte[] getDigestResult(Digest digest)
     {
         byte[] hash = new byte[DigestUtil.getDigestSize(digest)];
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSMTSignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSMTSignatureSpi.java
index e352f30..aeda0fc 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSMTSignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSMTSignatureSpi.java
@@ -169,6 +169,15 @@
         return rKey;
     }
 
+    static public class generic
+        extends XMSSMTSignatureSpi
+    {
+        public generic()
+        {
+            super("XMSSMT", new NullDigest(), new XMSSMTSigner());
+        }
+    }
+    
     static public class withSha256
         extends XMSSMTSignatureSpi
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSSignatureSpi.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSSignatureSpi.java
index ed01424..7b7628c 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSSignatureSpi.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/provider/xmss/XMSSSignatureSpi.java
@@ -168,6 +168,15 @@
         return rKey;
     }
 
+    static public class generic
+        extends XMSSSignatureSpi
+    {
+        public generic()
+        {
+            super("XMSS", new NullDigest(), new XMSSSigner());
+        }
+    }
+
     static public class withSha256
         extends XMSSSignatureSpi
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSKeyGenParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSKeyGenParameterSpec.java
new file mode 100644
index 0000000..717bd1f
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSKeyGenParameterSpec.java
@@ -0,0 +1,37 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * ParameterSpec for keys using the LMS Hierarchical Signature System (HSS).
+ */
+public class LMSHSSKeyGenParameterSpec
+    implements AlgorithmParameterSpec
+{
+    private final LMSKeyGenParameterSpec[] specs;
+
+    /**
+     * Base constructor, specify the LMS parameters at each level of the hierarchy.
+     *
+     * @param specs the LMS parameter specs for each level of the hierarchy.
+     */
+    public LMSHSSKeyGenParameterSpec(LMSKeyGenParameterSpec... specs)
+    {
+        if (specs.length == 0)
+        {
+            throw new IllegalArgumentException("at least one LMSKeyGenParameterSpec required");
+        }
+
+        this.specs = specs.clone();
+    }
+
+    /**
+     * Return the LMS parameters for the HSS hierarchy.
+     *
+     * @return the HSS component LMS parameter specs.
+     */
+    public LMSKeyGenParameterSpec[] getLMSSpecs()
+    {
+        return specs.clone();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSParameterSpec.java
new file mode 100644
index 0000000..7a7e04a
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSHSSParameterSpec.java
@@ -0,0 +1,33 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * ParameterSpec for keys using the LMS Hierarchical Signature System (HSS).
+ * @deprecated use LMSKeyGenParameterSpec
+ */
+public class LMSHSSParameterSpec
+    implements AlgorithmParameterSpec
+{
+    private final LMSParameterSpec[] specs;
+
+    /**
+     * Base constructor, specify the LMS parameters at each level of the hierarchy.
+     *
+     * @param specs the LMS parameter specs for each level of the hierarchy.
+     */
+    public LMSHSSParameterSpec(LMSParameterSpec[] specs)
+    {
+        this.specs = specs.clone();
+    }
+
+    /**
+     * Return the LMS parameters for the HSS hierarchy.
+     *
+     * @return the HSS component LMS parameter specs.
+     */
+    public LMSParameterSpec[] getLMSSpecs()
+    {
+        return specs.clone();
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSKeyGenParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSKeyGenParameterSpec.java
new file mode 100644
index 0000000..3f395ab
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSKeyGenParameterSpec.java
@@ -0,0 +1,48 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
+
+/**
+ * ParameterSpec for the Leighton-Micali Hash-Based Signature (LMS) scheme.
+ */
+public class LMSKeyGenParameterSpec
+    implements AlgorithmParameterSpec
+{
+    private final LMSigParameters lmSigParams;
+    private final LMOtsParameters lmOtsParameters;
+
+    /**
+     * Base constructor.
+     *
+     * @param lmSigParams  the LMS system signature parameters to use.
+     * @param lmOtsParameters the LM OTS parameters to use for the underlying one-time signature keys.
+     */
+    public LMSKeyGenParameterSpec(LMSigParameters lmSigParams, LMOtsParameters lmOtsParameters)
+    {
+        this.lmSigParams = lmSigParams;
+        this.lmOtsParameters = lmOtsParameters;
+    }
+
+    /**
+     * Return the LMS system signature parameters.
+     *
+     * @return the LMS system signature parameters.
+     */
+    public LMSigParameters getSigParams()
+    {
+        return lmSigParams;
+    }
+
+    /**
+     * Return the LM OTS parameters to use for the underlying one-time signature keys.
+     * 
+     * @return the LM OTS parameters.
+     */
+    public LMOtsParameters getOtsParams()
+    {
+        return lmOtsParameters;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSParameterSpec.java
new file mode 100644
index 0000000..e518087
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/LMSParameterSpec.java
@@ -0,0 +1,49 @@
+package org.bouncycastle.pqc.jcajce.spec;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+import org.bouncycastle.pqc.crypto.lms.LMOtsParameters;
+import org.bouncycastle.pqc.crypto.lms.LMSigParameters;
+
+/**
+ * ParameterSpec for the Leighton-Micali Hash-Based Signature (LMS) scheme.
+ * @deprecated use LMSKeyGenParameterSpec
+ */
+public class LMSParameterSpec
+    implements AlgorithmParameterSpec
+{
+    private final LMSigParameters lmSigParams;
+    private final LMOtsParameters lmOtsParameters;
+
+    /**
+     * Base constructor.
+     *
+     * @param lmSigParams  the LMS system signature parameters to use.
+     * @param lmOtsParameters the LM OTS parameters to use for the underlying one-time signature keys.
+     */
+    public LMSParameterSpec(LMSigParameters lmSigParams, LMOtsParameters lmOtsParameters)
+    {
+        this.lmSigParams = lmSigParams;
+        this.lmOtsParameters = lmOtsParameters;
+    }
+
+    /**
+     * Return the LMS system signature parameters.
+     *
+     * @return the LMS system signature parameters.
+     */
+    public LMSigParameters getSigParams()
+    {
+        return lmSigParams;
+    }
+
+    /**
+     * Return the LM OTS parameters to use for the underlying one-time signature keys.
+     * 
+     * @return the LM OTS parameters.
+     */
+    public LMOtsParameters getOtsParams()
+    {
+        return lmOtsParameters;
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java
index 85a71e4..c2acf83 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/RainbowParameterSpec.java
@@ -11,7 +11,7 @@
  * More detailed information about the needed parameters for the Rainbow
  * Signature Scheme is to be found in the paper of Jintai Ding, Dieter Schmidt:
  * Rainbow, a New Multivariable Polynomial Signature Scheme. ACNS 2005: 164-175
- * (http://dx.doi.org/10.1007/11496137_12)
+ * (https://dx.doi.org/10.1007/11496137_12)
  * </p>
  */
 public class RainbowParameterSpec
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSMTParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSMTParameterSpec.java
index d905522..5277484 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSMTParameterSpec.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSMTParameterSpec.java
@@ -25,6 +25,42 @@
      */
     public static final String SHAKE256 = "SHAKE256";
 
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_20d2_256 = new XMSSMTParameterSpec(20, 2, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_20d4_256 = new XMSSMTParameterSpec(20, 4, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d2_256 = new XMSSMTParameterSpec(40, 2, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d4_256 = new XMSSMTParameterSpec(40, 4, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d8_256 = new XMSSMTParameterSpec(40, 8, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d3_256 = new XMSSMTParameterSpec(60, 3, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d6_256 = new XMSSMTParameterSpec(60, 6, SHA256);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d12_256 = new XMSSMTParameterSpec(60, 12, SHA256);
+
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_20d2_512 = new XMSSMTParameterSpec(20, 2, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_20d4_512 = new XMSSMTParameterSpec(20, 4, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d2_512 = new XMSSMTParameterSpec(40, 2, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d4_512 = new XMSSMTParameterSpec(40, 4, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_40d8_512 = new XMSSMTParameterSpec(40, 8, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d3_512 = new XMSSMTParameterSpec(60, 3, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d6_512 = new XMSSMTParameterSpec(60, 6, SHA512);
+    public static final XMSSMTParameterSpec XMSSMT_SHA2_60d12_512 = new XMSSMTParameterSpec(60, 12, SHA512);
+
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_20d2_256 = new XMSSMTParameterSpec(20, 2, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_20d4_256 = new XMSSMTParameterSpec(20, 4, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d2_256 = new XMSSMTParameterSpec(40, 2, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d4_256 = new XMSSMTParameterSpec(40, 4, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d8_256 = new XMSSMTParameterSpec(40, 8, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d3_256 = new XMSSMTParameterSpec(60, 3, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d6_256 = new XMSSMTParameterSpec(60, 6, SHAKE128);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d12_256 = new XMSSMTParameterSpec(60, 12, SHAKE128);
+
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_20d2_512 = new XMSSMTParameterSpec(20, 2, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_20d4_512 = new XMSSMTParameterSpec(20, 4, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d2_512 = new XMSSMTParameterSpec(40, 2, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d4_512 = new XMSSMTParameterSpec(40, 4, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_40d8_512 = new XMSSMTParameterSpec(40, 8, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d3_512 = new XMSSMTParameterSpec(60, 3, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d6_512 = new XMSSMTParameterSpec(60, 6, SHAKE256);
+    public static final XMSSMTParameterSpec XMSSMT_SHAKE_60d12_512 = new XMSSMTParameterSpec(60, 12, SHAKE256);
+
     private final int height;
     private final int layers;
     private final String treeDigest;
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSParameterSpec.java b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSParameterSpec.java
index 72cef09..968e21f 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSParameterSpec.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/jcajce/spec/XMSSParameterSpec.java
@@ -25,6 +25,23 @@
      */
     public static final String SHAKE256 = "SHAKE256";
 
+    /**
+     * Standard XMSS parameters
+     */
+    public static final XMSSParameterSpec SHA2_10_256 = new XMSSParameterSpec(10, SHA256);
+    public static final XMSSParameterSpec SHA2_16_256 = new XMSSParameterSpec(16, SHA256);
+    public static final XMSSParameterSpec SHA2_20_256 = new XMSSParameterSpec(20, SHA256);
+    public static final XMSSParameterSpec SHAKE_10_256 = new XMSSParameterSpec(10, SHAKE128);
+    public static final XMSSParameterSpec SHAKE_16_256 = new XMSSParameterSpec(16, SHAKE128);
+    public static final XMSSParameterSpec SHAKE_20_256 = new XMSSParameterSpec(20, SHAKE128);
+
+    public static final XMSSParameterSpec SHA2_10_512 = new XMSSParameterSpec(10, SHA512);
+    public static final XMSSParameterSpec SHA2_16_512 = new XMSSParameterSpec(16, SHA512);
+    public static final XMSSParameterSpec SHA2_20_512 = new XMSSParameterSpec(20, SHA512);
+    public static final XMSSParameterSpec SHAKE_10_512 = new XMSSParameterSpec(10, SHAKE256);
+    public static final XMSSParameterSpec SHAKE_16_512 = new XMSSParameterSpec(16, SHAKE256);
+    public static final XMSSParameterSpec SHAKE_20_512 = new XMSSParameterSpec(20, SHAKE256);
+
     private final int height;
     private final String treeDigest;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java
index d5d9c05..945e44e 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nONBElement.java
@@ -103,7 +103,7 @@
     private static final int MAXLONG = 64;
 
     /**
-     * holds the lenght of the polynomial with 64 bit sized fields.
+     * holds the length of the polynomial with 64 bit sized fields.
      */
     private int mLength;
 
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java
index 36b2033..68a9604 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/GF2nPolynomialElement.java
@@ -9,7 +9,7 @@
  * This class implements elements of finite binary fields <i>GF(2<sup>n</sup>)</i>
  * using polynomial representation. For more information on the arithmetic see
  * for example IEEE Standard 1363 or <a
- * href=http://www.certicom.com/research/online.html> Certicom online-tutorial</a>.
+ * href=https://www.certicom.com/research/online.html> Certicom online-tutorial</a>.
  *
  * @see "GF2nField"
  * @see GF2nPolynomialField
diff --git a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java
index e004bdc..ff0e071 100644
--- a/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java
+++ b/bcprov/src/main/java/org/bouncycastle/pqc/math/linearalgebra/IntegerFunctions.java
@@ -1355,8 +1355,6 @@
         {
             if (a[i - 1] >= a[i])
             {
-                System.out.println("a[" + (i - 1) + "] = " + a[i - 1] + " >= "
-                    + a[i] + " = a[" + i + "]");
                 return false;
             }
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Arrays.java b/bcprov/src/main/java/org/bouncycastle/util/Arrays.java
index 283616a..6e52c36 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/Arrays.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/Arrays.java
@@ -33,6 +33,27 @@
         return java.util.Arrays.equals(a, b);
     }
 
+    public static boolean areEqual(byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex)
+    {
+        int aLength = aToIndex - aFromIndex;
+        int bLength = bToIndex - bFromIndex;
+
+        if (aLength != bLength)
+        {
+            return false;
+        }
+
+        for (int i = 0; i < aLength; ++i)
+        {
+            if (a[aFromIndex + i] != b[bFromIndex + i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     public static boolean areEqual(char[] a, char[] b)
     {
         return java.util.Arrays.equals(a, b);
@@ -840,11 +861,13 @@
     {
         if (null == a)
         {
-            return b.clone();
+            // b might also be null
+            return clone(b);
         }
         if (null == b)
         {
-            return a.clone();
+            // a might also be null
+            return clone(a);
         }
 
         byte[] r = new byte[a.length + b.length];
@@ -853,6 +876,25 @@
         return r;
     }
 
+    public static short[] concatenate(short[] a, short[] b)
+    {
+        if (null == a)
+        {
+            // b might also be null
+            return clone(b);
+        }
+        if (null == b)
+        {
+            // a might also be null
+            return clone(a);
+        }
+
+        short[] r = new short[a.length + b.length];
+        System.arraycopy(a, 0, r, 0, a.length);
+        System.arraycopy(b, 0, r, a.length, b.length);
+        return r;
+    }
+
     public static byte[] concatenate(byte[] a, byte[] b, byte[] c)
     {
         if (null == a)
@@ -928,11 +970,13 @@
     {
         if (null == a)
         {
-            return b.clone();
+            // b might also be null
+            return clone(b);
         }
         if (null == b)
         {
-            return a.clone();
+            // a might also be null
+            return clone(a);
         }
 
         int[] r = new int[a.length + b.length];
@@ -1100,4 +1144,19 @@
         }
         return false;
     }
+
+    public static boolean isNullOrEmpty(byte[] array)
+    {
+        return null == array || array.length < 1;
+    }
+
+    public static boolean isNullOrEmpty(int[] array)
+    {
+        return null == array || array.length < 1;
+    }
+
+    public static boolean isNullOrEmpty(Object[] array)
+    {
+        return null == array || array.length < 1;
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/util/BigIntegers.java b/bcprov/src/main/java/org/bouncycastle/util/BigIntegers.java
index 077e6b6..46e2bb0 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/BigIntegers.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/BigIntegers.java
@@ -3,6 +3,9 @@
 import java.math.BigInteger;
 import java.security.SecureRandom;
 
+import org.bouncycastle.math.raw.Mod;
+import org.bouncycastle.math.raw.Nat;
+
 /**
  * BigInteger utilities.
  */
@@ -10,8 +13,8 @@
 {
     public static final BigInteger ZERO = BigInteger.valueOf(0);
     public static final BigInteger ONE = BigInteger.valueOf(1);
+    public static final BigInteger TWO = BigInteger.valueOf(2);
 
-    private static final BigInteger TWO = BigInteger.valueOf(2);
     private static final BigInteger THREE = BigInteger.valueOf(3);
 
     private static final int MAX_ITERATIONS = 1000;
@@ -19,7 +22,7 @@
     /**
      * Return the passed in value as an unsigned byte array.
      * 
-     * @param value value to be converted.
+     * @param value the value to be converted.
      * @return a byte array without a leading zero byte if present in the signed encoding.
      */
     public static byte[] asUnsignedByteArray(
@@ -27,7 +30,7 @@
     {
         byte[] bytes = value.toByteArray();
         
-        if (bytes[0] == 0)
+        if (bytes[0] == 0 && bytes.length != 1)
         {
             byte[] tmp = new byte[bytes.length - 1];
             
@@ -40,10 +43,14 @@
     }
 
     /**
-     * Return the passed in value as an unsigned byte array.
+     * Return the passed in value as an unsigned byte array of the specified length, padded with
+     * leading zeros as necessary..
      *
-     * @param value value to be converted.
-     * @return a byte array without a leading zero byte if present in the signed encoding.
+     * @param length
+     *            the fixed length of the result
+     * @param value
+     *            the value to be converted.
+     * @return a byte array padded to a fixed length with leading zeros.
      */
     public static byte[] asUnsignedByteArray(int length, BigInteger value)
     {
@@ -53,7 +60,7 @@
             return bytes;
         }
 
-        int start = bytes[0] == 0 ? 1 : 0;
+        int start = (bytes[0] == 0 && bytes.length != 1) ? 1 : 0;
         int count = bytes.length - start;
 
         if (count > length)
@@ -67,6 +74,41 @@
     }
 
     /**
+     * Write the passed in value as unsigned bytes to the specified buffer range, padded with
+     * leading zeros as necessary.
+     *
+     * @param value
+     *            the value to be converted.
+     * @param buf
+     *            the buffer to which the value is written.
+     * @param off
+     *            the start offset in array <code>buf</code> at which the data is written.
+     * @param len
+     *            the fixed length of data written (possibly padded with leading zeros).
+     */
+    public static void asUnsignedByteArray(BigInteger value, byte[] buf, int off, int len)
+    {
+        byte[] bytes = value.toByteArray();
+        if (bytes.length == len)
+        {
+            System.arraycopy(bytes, 0, buf, off, len);
+            return;
+        }
+
+        int start = (bytes[0] == 0 && bytes.length != 1) ? 1 : 0;
+        int count = bytes.length - start;
+
+        if (count > len)
+        {
+            throw new IllegalArgumentException("standard length exceeded for value");
+        }
+
+        int padLen = len - count;
+        Arrays.fill(buf,  off, off + padLen, (byte)0x00);
+        System.arraycopy(bytes, start, buf, off + padLen, count);
+    }
+
+    /**
      * Return a random BigInteger not less than 'min' and not greater than 'max'
      * 
      * @param min the least value that may be generated
@@ -146,8 +188,75 @@
         return x.longValue(); 
     }
 
+    public static BigInteger modOddInverse(BigInteger M, BigInteger X)
+    {
+        if (!M.testBit(0))
+        {
+            throw new IllegalArgumentException("'M' must be odd");
+        }
+        if (M.signum() != 1)
+        {
+            throw new ArithmeticException("BigInteger: modulus not positive");
+        }
+        if (X.signum() < 0 || X.compareTo(M) >= 0)
+        {
+            X = X.mod(M);
+        }
+
+        int bits = M.bitLength();
+        int[] m = Nat.fromBigInteger(bits, M);
+        int[] x = Nat.fromBigInteger(bits, X);
+        int len = m.length;
+        int[] z = Nat.create(len);
+        if (0 == Mod.modOddInverse(m, x, z))
+        {
+            throw new ArithmeticException("BigInteger not invertible.");
+        }
+        return Nat.toBigInteger(len, z);
+    }
+
+    public static BigInteger modOddInverseVar(BigInteger M, BigInteger X)
+    {
+        if (!M.testBit(0))
+        {
+            throw new IllegalArgumentException("'M' must be odd");
+        }
+        if (M.signum() != 1)
+        {
+            throw new ArithmeticException("BigInteger: modulus not positive");
+        }
+        if (M.equals(ONE))
+        {
+            return ZERO;
+        }
+        if (X.signum() < 0 || X.compareTo(M) >= 0)
+        {
+            X = X.mod(M);
+        }
+        if (X.equals(ONE))
+        {
+            return ONE;
+        }
+
+        int bits = M.bitLength();
+        int[] m = Nat.fromBigInteger(bits, M);
+        int[] x = Nat.fromBigInteger(bits, X);
+        int len = m.length;
+        int[] z = Nat.create(len);
+        if (!Mod.modOddInverseVar(m, x, z))
+        {
+            throw new ArithmeticException("BigInteger not invertible.");
+        }
+        return Nat.toBigInteger(len, z);
+    }
+
     public static int getUnsignedByteLength(BigInteger n)
     {
+        if (n.equals(ZERO))
+        {
+            return 1;
+        }
+
         return (n.bitLength() + 7) / 8;
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Doubles.java b/bcprov/src/main/java/org/bouncycastle/util/Doubles.java
new file mode 100644
index 0000000..d72bc57
--- /dev/null
+++ b/bcprov/src/main/java/org/bouncycastle/util/Doubles.java
@@ -0,0 +1,9 @@
+package org.bouncycastle.util;
+
+public class Doubles
+{
+    public static Double valueOf(double value)
+    {
+        return Double.valueOf(value);
+    }
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Integers.java b/bcprov/src/main/java/org/bouncycastle/util/Integers.java
index 23d9804..fdacb51 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/Integers.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/Integers.java
@@ -10,6 +10,21 @@
         return Integer.numberOfLeadingZeros(i);
     }
 
+    public static int numberOfTrailingZeros(int i)
+    {
+        return Integer.numberOfTrailingZeros(i);
+    }
+
+    public static int reverse(int i)
+    {
+        return Integer.reverse(i);
+    }
+
+    public static int reverseBytes(int i)
+    {
+        return Integer.reverseBytes(i);
+    }
+
     public static int rotateLeft(int i, int distance)
     {
         return Integer.rotateLeft(i, distance);
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Longs.java b/bcprov/src/main/java/org/bouncycastle/util/Longs.java
index 18f37c1..2e50210 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/Longs.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/Longs.java
@@ -2,6 +2,26 @@
 
 public class Longs
 {
+    public static long reverse(long i)
+    {
+        return Long.reverse(i);
+    }
+
+    public static long reverseBytes(long i)
+    {
+        return Long.reverseBytes(i);
+    }
+
+    public static long rotateLeft(long i, int distance)
+    {
+        return Long.rotateLeft(i, distance);
+    }
+
+    public static long rotateRight(long i, int distance)
+    {
+        return Long.rotateRight(i, distance);
+    }
+
     public static Long valueOf(long value)
     {
         return Long.valueOf(value);
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Pack.java b/bcprov/src/main/java/org/bouncycastle/util/Pack.java
index 44b0b27..d0eeef4 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/Pack.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/Pack.java
@@ -7,14 +7,14 @@
 {
     public static short bigEndianToShort(byte[] bs, int off)
     {
-        int n = (bs[  off] & 0xff) << 8;
+        int n = (bs[off] & 0xff) << 8;
         n |= (bs[++off] & 0xff);
         return (short)n;
     }
 
     public static int bigEndianToInt(byte[] bs, int off)
     {
-        int n = bs[  off] << 24;
+        int n = bs[off] << 24;
         n |= (bs[++off] & 0xff) << 16;
         n |= (bs[++off] & 0xff) << 8;
         n |= (bs[++off] & 0xff);
@@ -30,6 +30,15 @@
         }
     }
 
+    public static void bigEndianToInt(byte[] bs, int off, int[] ns, int nsOff, int nsLen)
+    {
+        for (int i = 0; i < nsLen; ++i)
+        {
+            ns[nsOff + i] = bigEndianToInt(bs, off);
+            off += 4;
+        }
+    }
+
     public static byte[] intToBigEndian(int n)
     {
         byte[] bs = new byte[4];
@@ -39,10 +48,10 @@
 
     public static void intToBigEndian(int n, byte[] bs, int off)
     {
-        bs[  off] = (byte)(n >>> 24);
+        bs[off] = (byte)(n >>> 24);
         bs[++off] = (byte)(n >>> 16);
-        bs[++off] = (byte)(n >>>  8);
-        bs[++off] = (byte)(n       );
+        bs[++off] = (byte)(n >>> 8);
+        bs[++off] = (byte)(n);
     }
 
     public static byte[] intToBigEndian(int[] ns)
@@ -61,6 +70,15 @@
         }
     }
 
+    public static void intToBigEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
+    {
+        for (int i = 0; i < nsLen; ++i)
+        {
+            intToBigEndian(ns[nsOff + i], bs, bsOff);
+            bsOff += 4;
+        }
+    }
+
     public static long bigEndianToLong(byte[] bs, int off)
     {
         int hi = bigEndianToInt(bs, off);
@@ -77,6 +95,15 @@
         }
     }
 
+    public static void bigEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen)
+    {
+        for (int i = 0; i < nsLen; ++i)
+        {
+            ns[nsOff + i] = bigEndianToLong(bs, bsOff);
+            bsOff += 8;
+        }
+    }
+
     public static byte[] longToBigEndian(long n)
     {
         byte[] bs = new byte[8];
@@ -106,16 +133,42 @@
         }
     }
 
+    public static void longToBigEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
+    {
+        for (int i = 0; i < nsLen; ++i)
+        {
+            longToBigEndian(ns[nsOff + i], bs, bsOff);
+            bsOff += 8;
+        }
+    }
+
+    /**
+     * @param value The number
+     * @param bs    The target.
+     * @param off   Position in target to start.
+     * @param bytes number of bytes to write.
+     * 
+     * @deprecated Will be removed
+     */
+    public static void longToBigEndian(long value, byte[] bs, int off, int bytes)
+    {
+        for (int i = bytes - 1; i >= 0; i--)
+        {
+            bs[i + off] = (byte)(value & 0xff);
+            value >>>= 8;
+        }
+    }
+
     public static short littleEndianToShort(byte[] bs, int off)
     {
-        int n = bs[  off] & 0xff;
+        int n = bs[off] & 0xff;
         n |= (bs[++off] & 0xff) << 8;
         return (short)n;
     }
 
     public static int littleEndianToInt(byte[] bs, int off)
     {
-        int n = bs[  off] & 0xff;
+        int n = bs[off] & 0xff;
         n |= (bs[++off] & 0xff) << 8;
         n |= (bs[++off] & 0xff) << 16;
         n |= bs[++off] << 24;
@@ -160,10 +213,25 @@
 
     public static void shortToLittleEndian(short n, byte[] bs, int off)
     {
-        bs[  off] = (byte)(n       );
-        bs[++off] = (byte)(n >>>  8);
+        bs[off] = (byte)(n);
+        bs[++off] = (byte)(n >>> 8);
     }
 
+
+    public static byte[] shortToBigEndian(short n)
+    {
+        byte[] r = new byte[2];
+        shortToBigEndian(n, r, 0);
+        return r;
+    }
+
+    public static void shortToBigEndian(short n, byte[] bs, int off)
+    {
+        bs[off] = (byte)(n >>> 8);
+        bs[++off] = (byte)(n);
+    }
+
+
     public static byte[] intToLittleEndian(int n)
     {
         byte[] bs = new byte[4];
@@ -173,8 +241,8 @@
 
     public static void intToLittleEndian(int n, byte[] bs, int off)
     {
-        bs[  off] = (byte)(n       );
-        bs[++off] = (byte)(n >>>  8);
+        bs[off] = (byte)(n);
+        bs[++off] = (byte)(n >>> 8);
         bs[++off] = (byte)(n >>> 16);
         bs[++off] = (byte)(n >>> 24);
     }
@@ -195,6 +263,15 @@
         }
     }
 
+    public static void intToLittleEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff)
+    {
+        for (int i = 0; i < nsLen; ++i)
+        {
+            intToLittleEndian(ns[nsOff + i], bs, bsOff);
+            bsOff += 4;
+        }
+    }
+
     public static long littleEndianToLong(byte[] bs, int off)
     {
         int lo = littleEndianToInt(bs, off);
diff --git a/bcprov/src/main/java/org/bouncycastle/util/Properties.java b/bcprov/src/main/java/org/bouncycastle/util/Properties.java
index fb63468..b87d3ac 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/Properties.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/Properties.java
@@ -4,6 +4,7 @@
 import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.security.Security;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -18,11 +19,10 @@
 {
     private Properties()
     {
-
     }
 
     private static final ThreadLocal threadProperties = new ThreadLocal();
-                          
+
     /**
      * Return whether a particular override has been set to true.
      *
@@ -33,15 +33,32 @@
     {
         try
         {
-            String p = getPropertyValue(propertyName);
-
-            if (p != null)
-            {
-                return "true".equals(Strings.toLowerCase(p));
-            }
-
+            return isSetTrue(getPropertyValue(propertyName));
+        }
+        catch (AccessControlException e)
+        {
             return false;
         }
+    }
+
+    /**
+     * Return whether a particular override has been set to false.
+     *
+     * @param propertyName the property name for the override.
+     * @param isTrue true if the override should be true, false otherwise.
+     * @return true if the property is set to the value of isTrue, false otherwise.
+     */
+    public static boolean isOverrideSetTo(String propertyName, boolean isTrue)
+    {
+        try
+        {
+            String propertyValue = getPropertyValue(propertyName);
+            if (isTrue)
+            {
+                return isSetTrue(propertyValue);
+            }
+            return isSetFalse(propertyValue);
+        }
         catch (AccessControlException e)
         {
             return false;
@@ -53,7 +70,7 @@
      *
      * @param propertyName the property name for the override.
      * @param enable true if the override should be enabled, false if it should be disabled.
-     * @return true if the override was already set, false otherwise.
+     * @return true if the override was already set true, false otherwise.
      */
     public static boolean setThreadOverride(String propertyName, boolean enable)
     {
@@ -63,43 +80,39 @@
         if (localProps == null)
         {
             localProps = new HashMap();
+
+            threadProperties.set(localProps);
         }
 
         localProps.put(propertyName, enable ? "true" : "false");
 
-        threadProperties.set(localProps);
-
         return isSet;
     }
 
     /**
-     * Enable the specified override property in the current thread only.
+     * Remove any value for the specified override property for the current thread only.
      *
      * @param propertyName the property name for the override.
-     * @return true if the override set true in thread local, false otherwise.
+     * @return true if the override was already set true in thread local, false otherwise.
      */
     public static boolean removeThreadOverride(String propertyName)
     {
-        boolean isSet = isOverrideSet(propertyName);
-
         Map localProps = (Map)threadProperties.get();
-        if (localProps == null)
+        if (localProps != null)
         {
-            return false;
+            String p = (String)localProps.remove(propertyName);
+            if (p != null)
+            {
+                if (localProps.isEmpty())
+                {
+                    threadProperties.remove();
+                }
+
+                return "true".equals(Strings.toLowerCase(p));
+            }
         }
 
-        localProps.remove(propertyName);
-
-        if (localProps.isEmpty())
-        {
-            threadProperties.remove();
-        }
-        else
-        {
-            threadProperties.set(localProps);
-        }
-
-        return isSet;
+        return false;
     }
 
     public static BigInteger asBigInteger(String propertyName)
@@ -134,18 +147,61 @@
 
     public static String getPropertyValue(final String propertyName)
     {
+        String val = (String)AccessController.doPrivileged(new PrivilegedAction()
+        {
+            public Object run()
+            {
+                return Security.getProperty(propertyName);
+            }
+        });
+        if (val != null)
+        {
+            return val;
+        }
+
+        Map localProps = (Map)threadProperties.get();
+        if (localProps != null)
+        {
+            String p = (String)localProps.get(propertyName);
+            if (p != null)
+            {
+                return p;
+            }
+        }
+
         return (String)AccessController.doPrivileged(new PrivilegedAction()
         {
             public Object run()
             {
-                Map localProps = (Map)threadProperties.get();
-                if (localProps != null)
-                {
-                    return localProps.get(propertyName);
-                }
-
                 return System.getProperty(propertyName);
             }
         });
     }
+
+    private static boolean isSetFalse(String p)
+    {
+        if (p == null || p.length() != 5)
+        {
+            return false;
+        }
+
+        return (p.charAt(0) == 'f' || p.charAt(0) == 'F')
+            && (p.charAt(1) == 'a' || p.charAt(1) == 'A')
+            && (p.charAt(2) == 'l' || p.charAt(2) == 'L')
+            && (p.charAt(3) == 's' || p.charAt(3) == 'S')
+            && (p.charAt(4) == 'e' || p.charAt(4) == 'E');
+    }
+
+    private static boolean isSetTrue(String p)
+    {
+        if (p == null || p.length() != 4)
+        {
+            return false;
+        }
+
+        return (p.charAt(0) == 't' || p.charAt(0) == 'T')
+            && (p.charAt(1) == 'r' || p.charAt(1) == 'R')
+            && (p.charAt(2) == 'u' || p.charAt(2) == 'U')
+            && (p.charAt(3) == 'e' || p.charAt(3) == 'E');
+    }
 }
diff --git a/bcprov/src/main/java/org/bouncycastle/util/encoders/Base64Encoder.java b/bcprov/src/main/java/org/bouncycastle/util/encoders/Base64Encoder.java
index 645b151..1af4860 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/encoders/Base64Encoder.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/encoders/Base64Encoder.java
@@ -49,71 +49,71 @@
     {
         initialiseDecodingTable();
     }
-    
+
+    public int encode(byte[] inBuf, int inOff, int inLen, byte[] outBuf, int outOff) throws IOException
+    {
+        int inPos = inOff;
+        int inEnd = inOff + inLen - 2;
+        int outPos = outOff;
+
+        while (inPos < inEnd)
+        {
+            int a1 = inBuf[inPos++];
+            int a2 = inBuf[inPos++] & 0xFF;
+            int a3 = inBuf[inPos++] & 0xFF;
+
+            outBuf[outPos++] = encodingTable[(a1 >>> 2) & 0x3F];
+            outBuf[outPos++] = encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3F];
+            outBuf[outPos++] = encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3F];
+            outBuf[outPos++] = encodingTable[a3 & 0x3F];
+        }
+
+        switch (inLen - (inPos - inOff))
+        {
+        case 1:
+        {
+            int a1 = inBuf[inPos++] & 0xFF;
+
+            outBuf[outPos++] = encodingTable[(a1 >>> 2) & 0x3F];
+            outBuf[outPos++] = encodingTable[(a1 << 4) & 0x3F];
+            outBuf[outPos++] = padding;
+            outBuf[outPos++] = padding;
+            break;
+        }
+        case 2:
+        {
+            int a1 = inBuf[inPos++] & 0xFF;
+            int a2 = inBuf[inPos++] & 0xFF;
+
+            outBuf[outPos++] = encodingTable[(a1 >>> 2) & 0x3F];
+            outBuf[outPos++] = encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3F];
+            outBuf[outPos++] = encodingTable[(a2 << 2) & 0x3F];
+            outBuf[outPos++] = padding;
+            break;
+        }
+        }
+
+        return outPos - outOff;
+    }
+
     /**
      * encode the input data producing a base 64 output stream.
      *
      * @return the number of bytes produced.
      */
-    public int encode(
-        byte[]                data,
-        int                    off,
-        int                    length,
-        OutputStream    out) 
+    public int encode(byte[] buf, int off, int len, OutputStream out) 
         throws IOException
     {
-        int modulus = length % 3;
-        int dataLength = (length - modulus);
-        int a1, a2, a3;
-        
-        for (int i = off; i < off + dataLength; i += 3)
+        byte[] tmp = new byte[72];
+        while (len > 0)
         {
-            a1 = data[i] & 0xff;
-            a2 = data[i + 1] & 0xff;
-            a3 = data[i + 2] & 0xff;
-
-            out.write(encodingTable[(a1 >>> 2) & 0x3f]);
-            out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]);
-            out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]);
-            out.write(encodingTable[a3 & 0x3f]);
+            int inLen = Math.min(54, len);
+            int outLen = encode(buf, off, inLen, tmp, 0);
+            out.write(tmp, 0, outLen);
+            off += inLen;
+            len -= inLen;
         }
-
-        /*
-         * process the tail end.
-         */
-        int    b1, b2, b3;
-        int    d1, d2;
-
-        switch (modulus)
-        {
-        case 0:        /* nothing left to do */
-            break;
-        case 1:
-            d1 = data[off + dataLength] & 0xff;
-            b1 = (d1 >>> 2) & 0x3f;
-            b2 = (d1 << 4) & 0x3f;
-
-            out.write(encodingTable[b1]);
-            out.write(encodingTable[b2]);
-            out.write(padding);
-            out.write(padding);
-            break;
-        case 2:
-            d1 = data[off + dataLength] & 0xff;
-            d2 = data[off + dataLength + 1] & 0xff;
-
-            b1 = (d1 >>> 2) & 0x3f;
-            b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
-            b3 = (d2 << 2) & 0x3f;
-
-            out.write(encodingTable[b1]);
-            out.write(encodingTable[b2]);
-            out.write(encodingTable[b3]);
-            out.write(padding);
-            break;
-        }
-
-        return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4);
+        return ((len + 2) / 3) * 4;
     }
 
     private boolean ignore(
@@ -136,6 +136,8 @@
         throws IOException
     {
         byte    b1, b2, b3, b4;
+        byte[]  outBuffer = new byte[54];   // S/MIME standard
+        int     bufOff = 0;
         int     outLen = 0;
         
         int     end = off + length;
@@ -191,16 +193,27 @@
             {
                 throw new IOException("invalid characters encountered in base64 data");
             }
+
+            outBuffer[bufOff++] = (byte)((b1 << 2) | (b2 >> 4));
+            outBuffer[bufOff++] = (byte)((b2 << 4) | (b3 >> 2));
+            outBuffer[bufOff++] = (byte)((b3 << 6) | b4);
             
-            out.write((b1 << 2) | (b2 >> 4));
-            out.write((b2 << 4) | (b3 >> 2));
-            out.write((b3 << 6) | b4);
+            if (bufOff == outBuffer.length)
+            {
+                out.write(outBuffer);
+                bufOff = 0;
+            }
             
             outLen += 3;
             
             i = nextI(data, i, finish);
         }
 
+        if (bufOff > 0)
+        {
+            out.write(outBuffer, 0, bufOff);
+        }
+
         int e0 = nextI(data, i, end);
         int e1 = nextI(data, e0 + 1, end);
         int e2 = nextI(data, e1 + 1, end);
@@ -232,6 +245,8 @@
         throws IOException
     {
         byte    b1, b2, b3, b4;
+        byte[]  outBuffer = new byte[54];   // S/MIME standard
+        int     bufOff = 0;
         int     length = 0;
         
         int     end = data.length();
@@ -288,15 +303,25 @@
                 throw new IOException("invalid characters encountered in base64 data");
             }
                
-            out.write((b1 << 2) | (b2 >> 4));
-            out.write((b2 << 4) | (b3 >> 2));
-            out.write((b3 << 6) | b4);
+            outBuffer[bufOff++] = (byte)((b1 << 2) | (b2 >> 4));
+            outBuffer[bufOff++] = (byte)((b2 << 4) | (b3 >> 2));
+            outBuffer[bufOff++] = (byte)((b3 << 6) | b4);
             
             length += 3;
-            
+            if (bufOff == outBuffer.length)
+            {
+                out.write(outBuffer);
+                bufOff = 0;
+            }
+
             i = nextI(data, i, finish);
         }
 
+        if (bufOff > 0)
+        {
+            out.write(outBuffer, 0, bufOff);
+        }
+
         int e0 = nextI(data, i, end);
         int e1 = nextI(data, e0 + 1, end);
         int e2 = nextI(data, e1 + 1, end);
diff --git a/bcprov/src/main/java/org/bouncycastle/util/encoders/HexEncoder.java b/bcprov/src/main/java/org/bouncycastle/util/encoders/HexEncoder.java
index 7c87c16..6bdafaf 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/encoders/HexEncoder.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/encoders/HexEncoder.java
@@ -45,27 +45,41 @@
         initialiseDecodingTable();
     }
 
+    public int encode(byte[] inBuf, int inOff, int inLen, byte[] outBuf, int outOff) throws IOException
+    {
+        int inPos = inOff;
+        int inEnd = inOff + inLen;
+        int outPos = outOff;
+
+        while (inPos < inEnd)
+        {
+            int b = inBuf[inPos++] & 0xFF;
+
+            outBuf[outPos++] = encodingTable[b >>> 4];
+            outBuf[outPos++] = encodingTable[b & 0xF];
+        }
+
+        return outPos - outOff;
+    }
+
     /**
      * encode the input data producing a Hex output stream.
      *
      * @return the number of bytes produced.
      */
-    public int encode(
-        byte[]                data,
-        int                    off,
-        int                    length,
-        OutputStream    out) 
+    public int encode(byte[] buf, int off, int len, OutputStream out) 
         throws IOException
-    {        
-        for (int i = off; i < (off + length); i++)
+    {
+        byte[] tmp = new byte[72];
+        while (len > 0)
         {
-            int    v = data[i] & 0xff;
-
-            out.write(encodingTable[(v >>> 4)]);
-            out.write(encodingTable[v & 0xf]);
+            int inLen = Math.min(36, len);
+            int outLen = encode(buf, off, inLen, tmp, 0);
+            out.write(tmp, 0, outLen);
+            off += inLen;
+            len -= inLen;
         }
-
-        return length * 2;
+        return len * 2;
     }
 
     private static boolean ignore(
@@ -89,6 +103,8 @@
     {
         byte    b1, b2;
         int     outLen = 0;
+        byte[]  buf = new byte[36];
+        int     bufOff = 0;
 
         int     end = off + length;
 
@@ -124,11 +140,21 @@
                 throw new IOException("invalid characters encountered in Hex data");
             }
 
-            out.write((b1 << 4) | b2);
+            buf[bufOff++] = (byte)((b1 << 4) | b2);
 
+            if (bufOff == buf.length)
+            {
+                out.write(buf);
+                bufOff = 0;
+            }
             outLen++;
         }
 
+        if (bufOff > 0)
+        {
+            out.write(buf, 0, bufOff);
+        }
+
         return outLen;
     }
 
@@ -145,7 +171,9 @@
     {
         byte    b1, b2;
         int     length = 0;
-
+        byte[]  buf = new byte[36];
+        int     bufOff = 0;
+        
         int     end = data.length();
 
         while (end > 0)
@@ -180,11 +208,22 @@
                 throw new IOException("invalid characters encountered in Hex string");
             }
 
-            out.write((b1 << 4) | b2);
+            buf[bufOff++] = (byte)((b1 << 4) | b2);
+
+            if (bufOff == buf.length)
+            {
+                out.write(buf);
+                bufOff = 0;
+            }
 
             length++;
         }
 
+        if (bufOff > 0)
+        {
+            out.write(buf, 0, bufOff);
+        }
+
         return length;
     }
 
diff --git a/bcprov/src/main/java/org/bouncycastle/util/io/TeeInputStream.java b/bcprov/src/main/java/org/bouncycastle/util/io/TeeInputStream.java
index 96da169..c59881c 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/io/TeeInputStream.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/io/TeeInputStream.java
@@ -25,6 +25,11 @@
         this.output = output;
     }
 
+    public int available() throws IOException
+    {
+        return input.available();
+    }
+
     public int read(byte[] buf)
         throws IOException
     {
diff --git a/bcprov/src/main/java/org/bouncycastle/util/test/SimpleTest.java b/bcprov/src/main/java/org/bouncycastle/util/test/SimpleTest.java
index 7d2c7c1..2af7f9b 100644
--- a/bcprov/src/main/java/org/bouncycastle/util/test/SimpleTest.java
+++ b/bcprov/src/main/java/org/bouncycastle/util/test/SimpleTest.java
@@ -168,6 +168,11 @@
         return Arrays.areEqual(a, b);
     }
 
+    protected boolean areEqual(byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex)
+    {
+        return Arrays.areEqual(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex);
+    }
+
     public TestResult perform()
     {
         try
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerException.java b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerException.java
index 173d478..be4d7b9 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerException.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerException.java
@@ -33,7 +33,7 @@
         {
             throw new IllegalArgumentException();
         }
-        if (index < -1 || (certPath != null && index >= certPath.getCertificates().size()))
+        if (index < -1 || index >= certPath.getCertificates().size())
         {
             throw new IndexOutOfBoundsException();
         }
@@ -51,7 +51,7 @@
         {
             throw new IllegalArgumentException();
         }
-        if (index < -1 || (certPath != null && index >= certPath.getCertificates().size()))
+        if (index < -1 || index >= certPath.getCertificates().size())
         {
             throw new IndexOutOfBoundsException();
         }
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages.properties b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages.properties
index 6843d2c..7749f3c 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages.properties
+++ b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages.properties
@@ -74,10 +74,10 @@
 ## path length errors
 
 # max path length extended
-CertPathReviewer.pathLenghtExtended.title = Maximum path length extended 
-CertPathReviewer.pathLenghtExtended.text = Certificate path invalid: Maximum path length extended.
-CertPathReviewer.pathLenghtExtended.summary = Certificate path invalid: Maximum path length extended.
-CertPathReviewer.pathLenghtExtended.details = Certificate path invalid: Maximum path length extended.
+CertPathReviewer.pathLengthExtended.title = Maximum path length extended 
+CertPathReviewer.pathLengthExtended.text = Certificate path invalid: Maximum path length extended.
+CertPathReviewer.pathLengthExtended.summary = Certificate path invalid: Maximum path length extended.
+CertPathReviewer.pathLengthExtended.details = Certificate path invalid: Maximum path length extended.
 
 # error reading length constraint from basic constraint extension
 CertPathReviewer.processLengthConstError.title = Path length checking failed
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages_de.properties b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages_de.properties
index b9398ea..8a8d4fa 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages_de.properties
+++ b/bcprov/src/main/java/org/bouncycastle/x509/CertPathReviewerMessages_de.properties
@@ -74,10 +74,10 @@
 ## path length errors
 
 # max path length extended
-CertPathReviewer.pathLenghtExtended.title = Maximale Pfadlänge überschritten 
-CertPathReviewer.pathLenghtExtended.text = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
-CertPathReviewer.pathLenghtExtended.summary = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
-CertPathReviewer.pathLenghtExtended.details = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
+CertPathReviewer.pathLengthExtended.title = Maximale Pfadlänge überschritten 
+CertPathReviewer.pathLengthExtended.text = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
+CertPathReviewer.pathLengthExtended.summary = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
+CertPathReviewer.pathLengthExtended.details = Zertifizierungspfad ungültig: die Maximale Pfadlänge ist überschritten.
 
 # error reading length constraint from basic constraint extension
 CertPathReviewer.processLengthConstError.title = Prüfen der Pfadlänge fehlgeschlagen
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/CertPathValidatorUtilities.java b/bcprov/src/main/java/org/bouncycastle/x509/CertPathValidatorUtilities.java
index 94a26e1..949b62a 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/CertPathValidatorUtilities.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/CertPathValidatorUtilities.java
@@ -61,8 +61,6 @@
 
 class CertPathValidatorUtilities
 {
-    protected static final PKIXCRLUtil CRL_UTIL = new PKIXCRLUtil();
-
     protected static final String CERTIFICATE_POLICIES = Extension.certificatePolicies.getId();
     protected static final String BASIC_CONSTRAINTS = Extension.basicConstraints.getId();
     protected static final String POLICY_MAPPINGS = Extension.policyMappings.getId();
@@ -73,14 +71,13 @@
     protected static final String ISSUING_DISTRIBUTION_POINT = Extension.issuingDistributionPoint.getId();
     protected static final String DELTA_CRL_INDICATOR = Extension.deltaCRLIndicator.getId();
     protected static final String POLICY_CONSTRAINTS = Extension.policyConstraints.getId();
-    protected static final String FRESHEST_CRL = Extension.freshestCRL.getId();
-    protected static final String CRL_DISTRIBUTION_POINTS = Extension.cRLDistributionPoints.getId();
-    protected static final String AUTHORITY_KEY_IDENTIFIER = Extension.authorityKeyIdentifier.getId();
+//    protected static final String FRESHEST_CRL = Extension.freshestCRL.getId();
+//    protected static final String CRL_DISTRIBUTION_POINTS = Extension.cRLDistributionPoints.getId();
+//    protected static final String AUTHORITY_KEY_IDENTIFIER = Extension.authorityKeyIdentifier.getId();
+    protected static final String CRL_NUMBER = Extension.cRLNumber.getId();
 
     protected static final String ANY_POLICY = "2.5.29.32.0";
 
-    protected static final String CRL_NUMBER = Extension.cRLNumber.getId();
-
     /*
     * key usage bits
     */
@@ -100,9 +97,6 @@
         "privilegeWithdrawn",
         "aACompromise"};
 
-
-
-
     /**
      * Returns the issuer of an attribute certificate or certificate.
      *
@@ -122,16 +116,11 @@
         }
     }
 
-    protected static Date getValidDate(PKIXParameters paramsPKIX)
+    protected static Date getValidityDate(PKIXParameters paramsPKIX, Date currentDate)
     {
-        Date validDate = paramsPKIX.getDate();
+        Date validityDate = paramsPKIX.getDate();
 
-        if (validDate == null)
-        {
-            validDate = new Date();
-        }
-
-        return validDate;
+        return null == validityDate ? currentDate : validityDate;
     }
 
     protected static X500Principal getSubjectPrincipal(X509Certificate cert)
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/PKIXCRLUtil.java b/bcprov/src/main/java/org/bouncycastle/x509/PKIXCRLUtil.java
index bfa9f2d..ebabf56 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/PKIXCRLUtil.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/PKIXCRLUtil.java
@@ -3,10 +3,6 @@
 import java.security.cert.CertStore;
 import java.security.cert.CertStoreException;
 import java.security.cert.PKIXParameters;
-import java.security.cert.X509CRL;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import java.util.Date;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -15,68 +11,17 @@
 import org.bouncycastle.jce.provider.AnnotatedException;
 import org.bouncycastle.util.StoreException;
 
-class PKIXCRLUtil
+abstract class PKIXCRLUtil
 {
-    public Set findCRLs(X509CRLStoreSelector crlselect, ExtendedPKIXParameters paramsPKIX, Date currentDate)
+    static Set findCRLs(X509CRLStoreSelector crlselect, PKIXParameters paramsPKIX)
         throws AnnotatedException
     {
-        Set initialSet = new HashSet();
+        HashSet completeSet = new HashSet();
 
         // get complete CRL(s)
         try
         {
-            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getAdditionalStores()));
-            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getStores()));
-            initialSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
-        }
-        catch (AnnotatedException e)
-        {
-            throw new AnnotatedException("Exception obtaining complete CRLs.", e);
-        }
-
-        Set finalSet = new HashSet();
-        Date validityDate = currentDate;
-
-        if (paramsPKIX.getDate() != null)
-        {
-            validityDate = paramsPKIX.getDate();
-        }
-
-        // based on RFC 5280 6.3.3
-        for (Iterator it = initialSet.iterator(); it.hasNext();)
-        {
-            X509CRL crl = (X509CRL)it.next();
-
-            if (crl.getNextUpdate().after(validityDate))
-            {
-                X509Certificate cert = crlselect.getCertificateChecking();
-
-                if (cert != null)
-                {
-                    if (crl.getThisUpdate().before(cert.getNotAfter()))
-                    {
-                        finalSet.add(crl);
-                    }
-                }
-                else
-                {
-                    finalSet.add(crl);
-                }
-            }
-        }
-
-        return finalSet;
-    }
-
-    public Set findCRLs(X509CRLStoreSelector crlselect, PKIXParameters paramsPKIX)
-        throws AnnotatedException
-    {
-        Set completeSet = new HashSet();
-
-        // get complete CRL(s)
-        try
-        {
-            completeSet.addAll(findCRLs(crlselect, paramsPKIX.getCertStores()));
+            findCRLs(completeSet, crlselect, paramsPKIX.getCertStores());
         }
         catch (AnnotatedException e)
         {
@@ -86,36 +31,30 @@
         return completeSet;
     }
 
-/**
-     * Return a Collection of all CRLs found in the X509Store's that are
-     * matching the crlSelect criteriums.
+    /**
+     * Add to a HashSet any and all CRLs found in the X509Store's that are matching the crlSelect
+     * criteria.
      *
-     * @param crlSelect a {@link X509CRLStoreSelector} object that will be used
-     *            to select the CRLs
-     * @param crlStores a List containing only
-     *            {@link org.bouncycastle.x509.X509Store  X509Store} objects.
+     * @param crls
+     *            the {@link HashSet} to add the CRLs to.
+     * @param crlSelect
+     *            a {@link X509CRLStoreSelector} object that will be used to select the CRLs
+     * @param crlStores
+     *            a List containing only {@link org.bouncycastle.x509.X509Store X509Store} objects.
      *            These are used to search for CRLs
-     *
-     * @return a Collection of all found {@link java.security.cert.X509CRL X509CRL} objects. May be
-     *         empty but never <code>null</code>.
      */
-    private final Collection findCRLs(X509CRLStoreSelector crlSelect,
-        List crlStores) throws AnnotatedException
+    private static void findCRLs(HashSet crls, X509CRLStoreSelector crlSelect, List crlStores) throws AnnotatedException
     {
-        Set crls = new HashSet();
-        Iterator iter = crlStores.iterator();
-
         AnnotatedException lastException = null;
         boolean foundValidStore = false;
 
+        Iterator iter = crlStores.iterator();
         while (iter.hasNext())
         {
             Object obj = iter.next();
-
             if (obj instanceof X509Store)
             {
                 X509Store store = (X509Store)obj;
-
                 try
                 {
                     crls.addAll(store.getMatches(crlSelect));
@@ -123,14 +62,12 @@
                 }
                 catch (StoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
             else
             {
                 CertStore store = (CertStore)obj;
-
                 try
                 {
                     crls.addAll(store.getCRLs(crlSelect));
@@ -138,8 +75,7 @@
                 }
                 catch (CertStoreException e)
                 {
-                    lastException = new AnnotatedException(
-                        "Exception searching in X.509 CRL store.", e);
+                    lastException = new AnnotatedException("Exception searching in X.509 CRL store.", e);
                 }
             }
         }
@@ -147,7 +83,5 @@
         {
             throw lastException;
         }
-        return crls;
     }
-
-}
\ No newline at end of file
+}
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java b/bcprov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
index 4c30550..8766474 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
@@ -75,6 +75,7 @@
 import org.bouncycastle.jce.provider.PKIXNameConstraintValidatorException;
 import org.bouncycastle.jce.provider.PKIXPolicyNode;
 import org.bouncycastle.util.Integers;
+import org.bouncycastle.util.Objects;
 
 /**
  * PKIXCertPathReviewer<br>
@@ -95,6 +96,7 @@
 
     protected PKIXParameters pkixParams;
 
+    protected Date currentDate;
     protected Date validDate;
 
     // state variables
@@ -152,7 +154,8 @@
 
         // b)
 
-        validDate = getValidDate(pkixParams);
+        currentDate = new Date();
+        validDate = getValidityDate(pkixParams, currentDate);
 
         // c) part of pkixParams
 
@@ -699,7 +702,7 @@
         // validation date
         {
             ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.certPathValidDate",
-                    new Object[] {new TrustedInput(validDate), new TrustedInput(new Date())});
+                    new Object[] {new TrustedInput(validDate), new TrustedInput(currentDate)});
             addNotification(msg);
         }
         
@@ -791,7 +794,7 @@
             if (sign != null)
             {
                 boolean[] ku = sign.getKeyUsage(); 
-                if (ku != null && !ku[5])
+                if (ku != null && (ku.length <= 5 || !ku[5]))
                 {
                     ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.trustKeyUsage");
                     addNotification(msg);
@@ -1052,9 +1055,9 @@
 
                 // n)
 
-                boolean[] _usage = cert.getKeyUsage();
+                boolean[] keyUsage = cert.getKeyUsage();
 
-                if ((_usage != null) && !_usage[KEY_CERT_SIGN])
+                if (keyUsage != null && (keyUsage.length <= KEY_CERT_SIGN || !keyUsage[KEY_CERT_SIGN]))
                 {
                     ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.noCertSign");
                     addError(msg,index);
@@ -2044,13 +2047,13 @@
         Iterator crl_iter;
         try 
         {
-            Collection crl_coll = CRL_UTIL.findCRLs(crlselect, paramsPKIX);
+            Collection crl_coll = PKIXCRLUtil.findCRLs(crlselect, paramsPKIX);
             crl_iter = crl_coll.iterator();
             
             if (crl_coll.isEmpty())
             {
-                // notifcation - no local crls found
-                crl_coll = CRL_UTIL.findCRLs(new X509CRLStoreSelector(),paramsPKIX);
+                // notification - no local crls found
+                crl_coll = PKIXCRLUtil.findCRLs(new X509CRLStoreSelector(),paramsPKIX);
                 Iterator it = crl_coll.iterator();
                 List nonMatchingCrlNames = new ArrayList();
                 while (it.hasNext())
@@ -2065,7 +2068,6 @@
                             Integers.valueOf(numbOfCrls)});
                 addNotification(msg,index);
             }
-
         }
         catch (AnnotatedException ae)
         {
@@ -2074,35 +2076,35 @@
             addError(msg,index);
             crl_iter = new ArrayList().iterator();
         }
+
         boolean validCrlFound = false;
         X509CRL crl = null;
         while (crl_iter.hasNext())
         {
             crl = (X509CRL)crl_iter.next();
-            
-            if (crl.getNextUpdate() == null
-                || paramsPKIX.getDate().before(crl.getNextUpdate()))
+
+            Date thisUpdate = crl.getThisUpdate();
+            Date nextUpdate = crl.getNextUpdate();
+            Object[] arguments = new Object[]{ new TrustedInput(thisUpdate), new TrustedInput(nextUpdate) };
+
+            if (nextUpdate == null || validDate.before(crl.getNextUpdate()))
             {
                 validCrlFound = true;
-                ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
-                        "CertPathReviewer.localValidCRL",
-                        new Object[] {new TrustedInput(crl.getThisUpdate()), new TrustedInput(crl.getNextUpdate())});
+                ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.localValidCRL", arguments);
                 addNotification(msg,index);
                 break;
             }
-            else
-            {
-                ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
-                        "CertPathReviewer.localInvalidCRL",
-                        new Object[] {new TrustedInput(crl.getThisUpdate()), new TrustedInput(crl.getNextUpdate())});
-                addNotification(msg,index);
-            }
+
+            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.localInvalidCRL", arguments);
+            addNotification(msg,index);
         }
-        
+
         // if no valid crl was found in the CertStores try to get one from a
         // crl distribution point
         if (!validCrlFound)
         {
+            X500Principal certIssuer = cert.getIssuerX500Principal();
+
             X509CRL onlineCRL = null;
             Iterator urlIt = crlDistPointUrls.iterator();
             while (urlIt.hasNext())
@@ -2113,40 +2115,36 @@
                     onlineCRL = getCRL(location);
                     if (onlineCRL != null)
                     {
+                        X500Principal crlIssuer = onlineCRL.getIssuerX500Principal();
+
                         // check if crl issuer is correct
-                        if (!cert.getIssuerX500Principal().equals(onlineCRL.getIssuerX500Principal()))
+                        if (!certIssuer.equals(crlIssuer))
                         {
-                            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
-                                        "CertPathReviewer.onlineCRLWrongCA",
-                                        new Object[] {new UntrustedInput(onlineCRL.getIssuerX500Principal().getName()),
-                                                      new UntrustedInput(cert.getIssuerX500Principal().getName()),
-                                                      new UntrustedUrlInput(location)});
+                            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.onlineCRLWrongCA",
+                                new Object[]{ new UntrustedInput(crlIssuer.getName()), new UntrustedInput(certIssuer.getName()),
+                                    new UntrustedUrlInput(location) });
                             addNotification(msg,index);
                             continue;
                         }
-                        
-                        if (onlineCRL.getNextUpdate() == null
-                            || pkixParams.getDate().before(onlineCRL.getNextUpdate()))
+
+                        Date thisUpdate = onlineCRL.getThisUpdate();
+                        Date nextUpdate = onlineCRL.getNextUpdate();
+                        Object[] arguments = new Object[]{ new TrustedInput(thisUpdate), new TrustedInput(nextUpdate),
+                            new UntrustedUrlInput(location) };
+
+                        if (nextUpdate == null || validDate.before(nextUpdate))
                         {
                             validCrlFound = true;
-                            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
-                                    "CertPathReviewer.onlineValidCRL",
-                                    new Object[] {new TrustedInput(onlineCRL.getThisUpdate()),
-                                                  new TrustedInput(onlineCRL.getNextUpdate()),
-                                                  new UntrustedUrlInput(location)});
-                            addNotification(msg,index);
+                            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.onlineValidCRL",
+                                arguments);
+                            addNotification(msg, index);
                             crl = onlineCRL;
                             break;
                         }
-                        else
-                        {
-                            ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,
-                                    "CertPathReviewer.onlineInvalidCRL",
-                                    new Object[] {new TrustedInput(onlineCRL.getThisUpdate()),
-                                                  new TrustedInput(onlineCRL.getNextUpdate()),
-                                                  new UntrustedUrlInput(location)});
-                            addNotification(msg,index);
-                        }
+
+                        ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.onlineInvalidCRL",
+                            arguments);
+                        addNotification(msg, index);
                     }
                 }
                 catch (CertPathReviewerException cpre)
@@ -2162,10 +2160,9 @@
         {
             if (sign != null)
             {
-                boolean[] keyusage = sign.getKeyUsage();
+                boolean[] keyUsage = sign.getKeyUsage();
 
-                if (keyusage != null
-                    && (keyusage.length < 7 || !keyusage[CRL_SIGN]))
+                if (keyUsage != null && (keyUsage.length <= CRL_SIGN || !keyUsage[CRL_SIGN]))
                 {
                     ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.noCrlSigningPermited");
                     throw new CertPathReviewerException(msg);
@@ -2243,11 +2240,12 @@
             //
             // warn if a new crl is available
             //
-            if (crl.getNextUpdate() != null && crl.getNextUpdate().before(pkixParams.getDate()))
+            Date nextUpdate = crl.getNextUpdate();
+            if (!(nextUpdate == null || validDate.before(nextUpdate)))
             {
-                ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.crlUpdateAvailable",
-                        new Object[] {new TrustedInput(crl.getNextUpdate())});
-                addNotification(msg,index);
+                ErrorBundle msg = new ErrorBundle(RESOURCE_NAME, "CertPathReviewer.crlUpdateAvailable",
+                    new Object[]{ new TrustedInput(nextUpdate) });
+                addNotification(msg, index);
             }
             
             //
@@ -2303,7 +2301,7 @@
                 Iterator it;
                 try 
                 {
-                    it  = CRL_UTIL.findCRLs(baseSelect, paramsPKIX).iterator();
+                    it  = PKIXCRLUtil.findCRLs(baseSelect, paramsPKIX).iterator();
                 }
                 catch (AnnotatedException ae)
                 {
@@ -2324,25 +2322,14 @@
                         ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.distrPtExtError");
                         throw new CertPathReviewerException(msg,ae);
                     }
-                    
-                    if (idp == null)
+
+                    if (Objects.areEqual(idp, baseIdp))
                     {
-                        if (baseIdp == null)
-                        {
-                            foundBase = true;
-                            break;
-                        }
-                    }
-                    else
-                    {
-                        if (idp.equals(baseIdp))
-                        {
-                            foundBase = true;
-                            break;
-                        }
+                        foundBase = true;
+                        break;
                     }
                 }
-                
+
                 if (!foundBase)
                 {
                     ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.noBaseCRL");
@@ -2389,7 +2376,6 @@
             ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.noValidCrlFound");
             throw new CertPathReviewerException(msg);
         }
-    
     }
     
     protected Vector getCRLDistUrls(CRLDistPoint crlDistPoints)
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/X509Util.java b/bcprov/src/main/java/org/bouncycastle/x509/X509Util.java
index d002111..c72169e 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/X509Util.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/X509Util.java
@@ -97,11 +97,12 @@
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA384);
         noParams.add(X9ObjectIdentifiers.ecdsa_with_SHA512);
         noParams.add(X9ObjectIdentifiers.id_dsa_with_sha1);
+        noParams.add(OIWObjectIdentifiers.dsaWithSHA1);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha224);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha256);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha384);
         noParams.add(NISTObjectIdentifiers.dsa_with_sha512);
-        
+
         //
         // RFC 4491
         //
diff --git a/bcprov/src/main/java/org/bouncycastle/x509/util/LDAPStoreHelper.java b/bcprov/src/main/java/org/bouncycastle/x509/util/LDAPStoreHelper.java
index 68e450e..7ee660f 100644
--- a/bcprov/src/main/java/org/bouncycastle/x509/util/LDAPStoreHelper.java
+++ b/bcprov/src/main/java/org/bouncycastle/x509/util/LDAPStoreHelper.java
@@ -56,7 +56,7 @@
  * </p><p>
  * For the used schemes see:
  * <ul>
- * <li><a href="http://www.ietf.org/rfc/rfc2587.txt">RFC 2587</a>
+ * <li><a href="https://www.ietf.org/rfc/rfc2587.txt">RFC 2587</a>
  * <li><a
  * href="http://www3.ietf.org/proceedings/01mar/I-D/pkix-ldap-schema-01.txt">Internet
  * X.509 Public Key Infrastructure Additional LDAP Schema for PKIs and PMIs</a>
diff --git a/bouncycastle.version b/bouncycastle.version
index 7f943a4..bab63ae 100644
--- a/bouncycastle.version
+++ b/bouncycastle.version
@@ -1,2 +1,2 @@
 BOUNCYCASTLE_JDK=15on
-BOUNCYCASTLE_VERSION=164
+BOUNCYCASTLE_VERSION=168